Canonical, direction-independent flow keys - #131
Merged
Conversation
netprotocols.flow adds FlowKey (a NamedTuple, since canonicalizing the two directions is comparing two (address, port) endpoint tuples, which a plain tuple already orders natively) and flow_key(), mirroring checksum.compute()'s signature shape: the transport layer positionally, the enclosing IPv4/IPv6 header as keyword-only ip=. Both directions of one TCP/UDP conversation produce the identical key: the two endpoints are always emitted lexicographically-smaller-first. Reads whichever of IPv4.protocol / IPv6.next_header the enclosing header has (same semantic field, different attribute name). A transport layer with no ports (ICMP) returns None from both the free function and Packet.flow_key() -- no invented port-slot convention, matching this codebase's established "None rather than raising" pattern. Packet.flow_key() walks self.layers for the first IPv4/IPv6 and TCP/UDP layers and delegates to the free function, mirroring with_checksums()'s enclosing-IP scan; None if either layer is missing. Closes#89. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVCFe7B1U5VeJRoUbx24vb
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Sep 4, 2026
Merged
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 freeto 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.
Summary
Closes#89.
netprotocols.flowis a new small module (FlowKey,flow_key(), mirroringchecksum.py's footprint) that folds a TCP/UDP segment and its enclosing IPv4/IPv6 header into a key that is identical for both directions of one conversation — a request and its reply key equal, whichever one you feed in.What's included
FlowKey— aNamedTuple(not a frozen dataclass): canonicalizing the two directions is comparing two(address, port)endpoint tuples and always emitting the lexicographically smaller one first, which a plain tuple already orders natively via<=. Every other frozen dataclass in this codebase models a wire format (decode()/__bytes__/_struct); a derived key isn't one, soNamedTupleis the right tool rather than boltingorder=Trueonto a dataclass.flow_key(layer, *, ip=None)— mirrorschecksum.compute()'s signature shape (transport layer positionally, enclosing IP keyword-only). Reads whichever ofIPv4.protocol/IPv6.next_headerthe enclosing header actually has (same semantic field, different attribute name).Packet.flow_key()— the convenience method. Walksself.layersfor the first IPv4/IPv6 and TCP/UDP layers (mirroring the exact enclosing-IP scanPacket.with_checksums()already uses) and delegates to the free function.Nonefrom both forms — not an exception, and no invented port-slot convention (NetFlow's ICMP type/code-into-port trick is real prior art but unlabeled magic here) — matching this codebase's established "Nonerather than raising" pattern (the NDP accessors inicmp.py).tests/test_flow.py: both directions of a TCP conversation produce equal keys (headers constructed independently, never assembled into aPacket); IPv4 and IPv6 both work; UDP works; ICMP returnsNonefrom both the free function andPacket.flow_key();FlowKeyis hashable and works as a dict key; a missingipfor a TCP/UDP layer raisesInvalidFieldError;Packet.flow_key()matches the free function and returnsNonewhen either layer is missing.ARCHITECTURE.md's layout map lists the new module.CHANGELOG.mdentry under## [Unreleased].Verification
uv run --frozen ruff check .anduv run --frozen ruff format --check .are cleanuv run --frozen mypyis clean (strict)uv run --frozen pytestpasses locallyuv run --frozen python scripts/benchmark.py --check --threshold 15— within threshold (+37.2% vs. baseline)CHANGELOG.mdhas an entry under## [Unreleased]No new protocol/dispatch change, so that checklist block doesn't apply.
Notes
Second of the three remaining Tier 2 issues (#90 → #89 → #92, per the roadmap's #107 working agreement) — merged one at a time, not auto-merged.
🤖 Generated with Claude Code
https://claude.ai/code/session_01SVCFe7B1U5VeJRoUbx24vb
Generated by Claude Code