feat(core): aim the tool at one address family, without touching the default - #169
Merged
Conversation
…to fit it The engine can now be told to touch IPv4 only or IPv6 only. An out-of-family packet takes the same exit as one outside the destination target: passed through untouched and NOT in scope. It is not blocked and not slowed, which is the whole distinction the switch has to survive being misread about. Adding this would have been a thirtieth way through decide(), and that function sat exactly on the pinned complexity ceiling, where the rule is that splitting lowers the number rather than raising it. Its three targeting tests asked one question and returned one identical verdict, so they moved into _out_of_scope() and the ceiling came down with them: 29 -> 27, measured, and the PLR0912 figure quoted next to it re-measured too rather than left to drift. 105 core and scope assertions pass unchanged across the fold. Both switches at once is a legal request meaning "nothing qualifies", stored as an empty set so the contradiction needs no case of its own. Saying out loud that it is probably a mistake belongs to apply_settings, next to the identical warning for LAN mode plus Internet only; refusing it would break a run somebody meant. An address the gate cannot read has no family and is left alone while one is chosen - the direction utils.is_lan_ip already takes. A mapped address (::ffff:1.2.3.4) counts as IPv6, because that is what it is on the wire.
…e reports Two BOOL fields in the DESTINATION card, next to the IP and Port they refine - not next to LAN mode and Internet only. Those two CUT traffic; these two only say what is aimed at, and a checkbox that borrows their place inherits their meaning: the first reading of "IPv4 only" under them is "IPv6 is blocked", which is the opposite of what it does. The registry entry, the CLI flag and the seven translation keys land in one commit because the guards refuse anything less: a field without --ipv4-only, or without a label in all three languages, reddens test_field_registry on its own. The flags are --ipv4-only / --ipv6-only, mirroring --internet-only, and no abbreviation collides (there is no other option starting with --o or --ip). Not narrows=True, deliberately: "IPv4 only" still reaches every IPv4 connection on the machine, so marking it as a bound would silence the blast-radius warning for a session that has bounded nothing. Both switches at once is logged once per apply, beside the identical warning for LAN mode plus Internet only, in the words that matter here: nothing will be IMPAIRED, rather than nothing will get through. The default is untouched and the rendered output says so: "Active: 10% loss." and `--loss 10`, with no fragment and no flag added.
Capturing IPv6 was not free here: the filters once began with `ip and ...`, which in the WinDivert language is IPv4 ONLY, so every IPv6 packet went past the tool uncounted and unlisted. A switch that can ask for exactly that state deserves a guard on the state it starts in, rather than a promise in a commit message. Four assertions, each on a surface a regression would show up on: every driver filter still carries both families, character for character; both fields default to off; a default session impairs an IPv4 and an IPv6 packet alike; and the summary and the reproduction command gain nothing at the default, with the opposite direction asserted too so it cannot pass by the flags never existing. The filter strings are literals rather than values read from the code under test. A guard that builds its expectation the way the code does agrees with the code by construction, including when the code is wrong. The GUI needed no code: the registry places both checkboxes on one row under IP and Port by itself. Verified on real Tk at 1366x768 in all three languages.
…them Both READMEs get the flag row next to --lan-mode, and the guard that demands it is why this is one commit and not a forgotten follow-up: test_cli_docs reddens on an undocumented flag. The user-facing line leads with what the switch does NOT do, because that is the half a reader gets wrong: the other family keeps flowing, nothing is blocked and nothing is slowed. The internal entry records what was deliberately left out - the WinDivert filter, and the reasons folding the family in there is its own change - so the next session does not read the omission as an oversight.
Eleven CLI runtime tests went red on a fake engine with no set_ip_family, and the failure looked nothing like its cause: the blast-radius warnings stopped appearing, because the run died before reaching them. The doubles are a consumer of the engine's interface, and they were the one consumer the analysis for this change did not enumerate. Adding the method is the whole fix - checked on master first, where the same file is green, so the question "is this mine or was it already broken" was answered by measurement rather than by assumption.
All three fired on CI, all three were right, and two of them are the opposite of what their numbers look like. build_arg_parser passed the size ceiling (135 > 133) when the two flags landed, and the ratchet's message says what to do rather than leaving it to taste: split it, do not raise the ceiling. The seam was already there. _add_scope_arguments now holds the ten flags that say WHICH traffic is aimed at - process, destination, address family, address class, blocking - and what stays behind says what is done to it. settings_summary had gone 25 -> 27 on two new branches, which is exactly the creep that ratchet exists to catch. Four plain on/off switches that each append one fixed phrase are now one loop over a table: back to 25, and a fifth switch costs nothing anything counts. COMPLEX_NEAR_CEILING moves 3 -> 5 and nothing grew into that band. Lowering max-complexity to 27 moved the 70% line from 20 to 18, so _capture_loop (20) and _module_level (19) are inside it without changing by a line. Re-measured and written down, because the count alone reads like a regression. The mutation that proves the guard can fail was re-anchored onto the new constant. Found by CI and not locally: the run here covered the surfaces this change touched, and these three scan the whole tree.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
The tool captures IPv4 and IPv6, and that was not free: the filters once began
with
ip and ..., which in the WinDivert language means IPv4 ONLY, so every IPv6packet went past uncounted and unlisted. This adds a way to aim at one family on
purpose, and treats the default it could undo as the thing most worth guarding.
The default is untouched, and there is a test that says so rather than a
promise. With neither switch on, every driver filter is unchanged character for
character, both families are impaired exactly as before, and the summary line and
the reproduction command gain nothing.
Where it lives, and why not where it first looked like it belonged
Two checkboxes in the destination card, under
IP:andPort::The obvious place was next to
LAN modeandInternet only. Measured, those twoare
impairs=alland genuinely CUT traffic indecide()step 2b, so a checkboxborrowing their place inherits their meaning: the first reading of "IPv4 only"
under them is "IPv6 is blocked", which is the opposite of what it does. The
destination card contains nothing that drops, and a family is a property of the
address beside it.
The tooltip leads with the half a reader gets wrong: the other family keeps
flowing, is not blocked and not slowed, and this applies with the IP field empty
too. The GUI needed no code - the registry places both on one row by itself.
How it behaves
target: passed through untouched,
scoped=False.empty set so the contradiction needs no case of its own. It is logged once per
apply, next to the identical warning for LAN mode plus Internet only, in the
words that matter here: nothing will be IMPAIRED, not nothing will get through.
chosen - the direction
utils.is_lan_ipalready takes.::ffff:1.2.3.4counts as IPv6, which is what it is on the wire.narrows=Falsedeliberately: one family still reaches every connection of thatfamily on the machine, so marking it as a bound would silence the blast-radius
warning for a session that has bounded nothing.
The ceiling, and what it forced
core.decidesat exactly on the pinned complexity ceiling of 29, where the rulebeside the number is that splitting lowers it rather than raising it. The family
gate would have been a thirtieth way through. Its three targeting tests asked one
question and returned one identical verdict, so they moved into
core._out_of_scope, andmax-complexitywent 29 -> 27 in the same change.The PLR0912 figure quoted next to it was re-measured (30 -> 28) rather than left
to drift. 105 core and scope assertions pass across the fold unchanged.
Deliberately not in here
The WinDivert filter.
decide()is the authority and folding a filter conjunctis the opt-in optimisation, exactly how the destination fields already work.
Doing it needs
pingparenthesised (icmp or icmpv6is a top-level OR), thecompile-length limit respected, and a rule for the contradictory pair - its own
change with its own measurements.
--simulategenerates IPv4 only, so--ipv6-onlyimpairs nothing there, which is the same answer a machine withoutIPv6 gives; recorded rather than papered over.
Verification
summary, view scope, GUI state, engine, docs (348 assertions)
ruff check .andmypy beantesterpython smoke_gui.pypython tools/ci_gui_render.pyon real Tk at 1366x768: en, pl and zh cleantooltip readable, Polish diacritics intact
Eleven CLI runtime tests went red on the way here, on a fake engine that did not
know the new setter. Checked against master first - green there - so "mine or
already broken" was answered by measurement. The doubles were the one consumer
the analysis had not enumerated.
No version bump.