Skip to content

Improve performance of packet dissection and build - #5085

Open
polybassa wants to merge 1 commit into
secdev:masterfrom
polybassa:perf-dissect-build
Open

Improve performance of packet dissection and build#5085
polybassa wants to merge 1 commit into
secdev:masterfrom
polybassa:perf-dissect-build

Conversation

@polybassa

Copy link
Copy Markdown
Contributor

Packet.setattr has to resolve field names before it can fall back to a plain slot assignment, and Packet.init goes through it 22 times for every layer that is dissected or built. It was the hottest function in both paths, at 18.8M calls per 21k dissected packets. Initialize the slots with object.setattr instead. Their types move to class-level annotations, since that form leaves nowhere to put an inline type comment.

PacketListField.getfield located the trailing Padding with "conf.padding_layer in p" followed by "p[conf.padding_layer]": two full recursive layer traversals per list element, ~2.7us for a two-layer element. Dissection always appends Padding as the last layer of the payload chain, so lastlayer() answers the same question in ~0.3us. This no longer descends into sub-packet fields, where a nested Padding was a false positive that truncated the list.

copy_fields_dict() and getfield_and_val() went through copy_field_value() and get_field(), spending two Python frames per field just to reach self.fieldtype[name]. Index it directly; both helpers remain for external callers.

Measured against master over 16 packet types, median of 6 interleaved rounds on a pinned core with a +/-0.6% noise floor:

dissect                              +7.7%
build (fresh packet)                +10.2%
dissect, PacketListField-heavy      +13.9%  (DNS/DHCP6/SCTP)
rebuild (dissected packet, cached)   +2.0%

No API changes. 6600 tests pass, mypy reports no new errors, and flake8 reports two fewer warnings. The 8 remaining test failures need live OpenLDAP/SMB servers and fail on master too.

AI-Assisted: yes (Claude Opus 5)

Packet.__setattr__ has to resolve field names before it can fall back to
a plain slot assignment, and Packet.__init__ goes through it 22 times for
every layer that is dissected or built. It was the hottest function in
both paths, at 18.8M calls per 21k dissected packets. Initialize the
slots with object.__setattr__ instead. Their types move to class-level
annotations, since that form leaves nowhere to put an inline type
comment.

PacketListField.getfield located the trailing Padding with
"conf.padding_layer in p" followed by "p[conf.padding_layer]": two full
recursive layer traversals per list element, ~2.7us for a two-layer
element. Dissection always appends Padding as the last layer of the
payload chain, so lastlayer() answers the same question in ~0.3us. This
no longer descends into sub-packet fields, where a nested Padding was a
false positive that truncated the list.

copy_fields_dict() and getfield_and_val() went through copy_field_value()
and get_field(), spending two Python frames per field just to reach
self.fieldtype[name]. Index it directly; both helpers remain for
external callers.

Measured against master over 16 packet types, median of 6 interleaved
rounds on a pinned core with a +/-0.6% noise floor:

    dissect                              +7.7%
    build (fresh packet)                +10.2%
    dissect, PacketListField-heavy      +13.9%  (DNS/DHCP6/SCTP)
    rebuild (dissected packet, cached)   +2.0%

No API changes. 6600 tests pass, mypy reports no new errors, and flake8
reports two fewer warnings. The 8 remaining test failures need live
OpenLDAP/SMB servers and fail on master too.

AI-Assisted: yes (Claude Opus 5)
Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.56%. Comparing base (b1a9799) to head (ec97aa4).
⚠️ Report is 6 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5085      +/-   ##
==========================================
+ Coverage   80.05%   80.56%   +0.51%     
==========================================
  Files         390      390              
  Lines       96810    96832      +22     
==========================================
+ Hits        77499    78015     +516     
+ Misses      19311    18817     -494     
Files with missing lines Coverage Δ
scapy/fields.py 92.82% <100.00%> (ø)
scapy/packet.py 84.96% <100.00%> (+0.15%) ⬆️

... and 18 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@polybassa
polybassa marked this pull request as ready for review August 14, 2026 16:51
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.

1 participant