From 48d20f6306c4e5092d8b9d22fcfec98b5d8483c1 Mon Sep 17 00:00:00 2001 From: Ugur Dogru Date: Mon, 29 Jun 2026 14:51:08 +0300 Subject: [PATCH 1/3] Add 802.11 HE Operation Info - 802.11ax-2021 9.4.2.249 - Add unit test AI-Assisted: yes (Codex) --- ...t11-extended-element-he-operation-notes.md | 46 +++++++ scapy/layers/dot11.py | 115 +++++++++++++++++- test/scapy/layers/dot11.uts | 46 +++++++ 3 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 scapy/layers/0001-dot11-extended-element-he-operation-notes.md diff --git a/scapy/layers/0001-dot11-extended-element-he-operation-notes.md b/scapy/layers/0001-dot11-extended-element-he-operation-notes.md new file mode 100644 index 00000000000..e545c07ac2b --- /dev/null +++ b/scapy/layers/0001-dot11-extended-element-he-operation-notes.md @@ -0,0 +1,46 @@ +# PR 0001: Dot11 Element ID Extension and HE Operation + +Scope: + +- Adds `Dot11EltExtension` dispatch for Element ID `255`. +- Adds `Dot11EltExtensionGeneric` for unknown or unsupported extension IDs. +- Adds `Dot11EltHEOperation` for Extended ID `36`. +- Adds `Dot11HE6GOperationInfo` when the HE Operation element indicates that + 6 GHz operation information is present. +- Adds focused `test/scapy/layers/dot11.uts` tests for generic, short, HE, and + HE 6 GHz extended elements. + +Spec references used in code comments: + +- IEEE Std 802.11ax-2021, `9.4.2.1` and Table `9-92`: Element ID Extension. +- IEEE Std 802.11ax-2021, `9.4.2.249`: HE Operation element. +- IEEE Std 802.11ax-2021, Figure `9-788k`: 6 GHz Operation Information field. + +Intentional partial support: + +- Element ID Extension is a registry for many 802.11 information elements. This + patch does not try to implement all of them. +- Unknown and unsupported extension IDs intentionally remain + `Dot11EltExtensionGeneric`. +- EHT Operation and Multi-Link are left for separate follow-up patches so this + first PR is reviewable on its own. + +Focused test command used: + +```sh +test/run_tests -t test/scapy/layers/dot11.uts -n 57-61 -F +``` + +Result: + +```text +PASSED=5 FAILED=0 +``` + +Full dot11 suite note: + +- A full `test/scapy/layers/dot11.uts` run reached and passed the existing + tests up through `Dot11EltVHTOperation in isolation` before I interrupted it + in this local environment because it was taking longer than expected. + The focused tests for this patch passed. + diff --git a/scapy/layers/dot11.py b/scapy/layers/dot11.py index 3e70571509e..420e6b6647d 100644 --- a/scapy/layers/dot11.py +++ b/scapy/layers/dot11.py @@ -995,7 +995,8 @@ def network_stats(self): 127: "Extended Capabilities", 191: "VHT Capabilities", 192: "VHT Operation", - 221: "Vendor Specific" + 221: "Vendor Specific", + 255: "Element ID Extension" } # Backward compatibility @@ -1555,6 +1556,118 @@ class Dot11EltVHTOperation(Dot11Elt): ] +# 802.11ax-2021 9.4.2.1, Table 9-92 + +class Dot11EltExtension(Dot11Elt): + # Element ID Extension is a registry for many IEs. Only HE Operation is + # specialized here; unknown and unsupported extension IDs stay generic. + name = "802.11 Element ID Extension" + match_subclass = True + ID = 255 + fields_desc = [ + ByteEnumField("ID", 255, _dot11_id_enum), + ByteField("len", 0), + ConditionalField( + ByteField("ext_ID", 0), + lambda pkt: pkt.len > 0 + ), + ] + + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if not _pkt or len(_pkt) < 3: + return cls + + length = orb(_pkt[1]) + ext_id = orb(_pkt[2]) + if ext_id == 36 and length >= 7: + return Dot11EltHEOperation + return Dot11EltExtensionGeneric + + +# 802.11ax-2021 9.4.2.1, Table 9-92 + +class Dot11EltExtensionGeneric(Dot11EltExtension): + name = "802.11 Element ID Extension" + match_subclass = True + ID = 255 + fields_desc = Dot11EltExtension.fields_desc + [ + StrLenField("info", b"", length_from=lambda pkt: max(pkt.len - 1, 0))] + + +# 802.11ax-2021 9.4.2.249, Figure 9-788k + +class Dot11HE6GOperationInfo(Packet): + name = "802.11 HE 6 GHz Operation Information" + fields_desc = [ + ByteField("primary_channel", 0), + # Control: 1B + BitField("reserved", 0, 2, tot_size=-1), + BitField("regulatory_info", 0, 3), + BitField("duplicate_beacon", 0, 1), + BitField("channel_width", 0, 2, end_tot_size=-1), + + ByteField("channel_center0", 0), + ByteField("channel_center1", 0), + ByteField("minimum_rate", 0), + ] + + def extract_padding(self, s): + return "", s + + +# 802.11ax-2021 9.4.2.249 + +class Dot11EltHEOperation(Dot11EltExtension): + name = "802.11 HE Operation" + match_subclass = True + ID = 255 + ext_ID = 36 + fields_desc = Dot11EltExtension.fields_desc + [ + # HE Operation Parameters: 3B + BitField("reserved", 0, 6, tot_size=-3), + BitField("six_g_op_info_present", 0, 1), + BitField("er_su_disable", 0, 1), + BitField("co_hosted_bss", 0, 1), + BitField("vht_operation_info_present", 0, 1), + BitField("txop_duration_rts_threshold", 0, 10), + BitField("twt_required", 0, 1), + BitField("default_pe_duration", 0, 3, end_tot_size=-3), + + # BSS Color Information: 1B + BitField("bss_color_disabled", 0, 1, tot_size=-1), + BitField("partial_bss_color", 0, 1), + BitField("bss_color", 0, 6, end_tot_size=-1), + # Basic HE-MCS and NSS Set: 2B + FieldListField( + "basic_he_mcs_and_nss_set", + [0x00], + BitField('SS', 0x00, size=2), + count_from=lambda x: 8 + ), + + ConditionalField( + PacketField( + "vht_operation_info", + Dot11VHTOperationInfo(), + Dot11VHTOperationInfo + ), + lambda p: p.vht_operation_info_present == 1), + + ConditionalField( + ByteField("max_co_hosted_bssid", 0), + lambda p: p.co_hosted_bss == 1), + + ConditionalField( + PacketField( + "he_6g_operation_info", + Dot11HE6GOperationInfo(), + Dot11HE6GOperationInfo + ), + lambda p: p.six_g_op_info_present == 1) + ] + + ###################### # 802.11 Frame types # ###################### diff --git a/test/scapy/layers/dot11.uts b/test/scapy/layers/dot11.uts index 65a59d0e907..9444eec4265 100644 --- a/test/scapy/layers/dot11.uts +++ b/test/scapy/layers/dot11.uts @@ -779,6 +779,52 @@ assert pkt[Dot11EltVHTOperation].VHT_Operation_Info.channel_center1 == 50 pkt = Dot11EltVHTOperation(b'\xc0\x05\x01*2\x00\x00') assert pkt[Dot11Elt::{"ID": 192}].len == 5 += Dot11EltExtension generic in isolation + +data = b'\xff\x03\x30\x01\x02' +pkt = Dot11Elt(data) +assert Dot11EltExtensionGeneric in pkt +assert pkt[Dot11EltExtensionGeneric].ID == 255 +assert pkt[Dot11EltExtensionGeneric].len == 3 +assert pkt[Dot11EltExtensionGeneric].ext_ID == 48 +assert pkt[Dot11EltExtensionGeneric].getfieldval("info") == b'\x01\x02' +assert raw(pkt) == data + += Dot11EltExtension short element in isolation + +pkt = Dot11Elt(b'\xff\x00') +assert Dot11EltExtension in pkt +assert pkt[Dot11EltExtension].ID == 255 +assert pkt[Dot11EltExtension].len == 0 +assert raw(pkt) == b'\xff\x00' + += Dot11EltHEOperation in isolation + +data = b'\xff\x07$\x00\x00\x00\x00\x00\x00' +pkt = Dot11Elt(data) +assert Dot11EltHEOperation in pkt +assert pkt[Dot11EltHEOperation].ID == 255 +assert pkt[Dot11EltHEOperation].len == 7 +assert pkt[Dot11EltHEOperation].ext_ID == 36 +assert pkt[Dot11EltHEOperation].six_g_op_info_present == 0 +assert pkt[Dot11EltHEOperation].vht_operation_info_present == 0 +assert pkt[Dot11EltHEOperation].basic_he_mcs_and_nss_set == [0] * 8 +assert raw(pkt) == data + += Dot11EltHEOperation with 6 GHz Operation Information in isolation + +data = b'\xff\x0c$\x00\x00\x02\x00\x00\x00\x05\x02%\x00\x0c' +pkt = Dot11Elt(data) +assert Dot11EltHEOperation in pkt +assert Dot11HE6GOperationInfo in pkt +assert pkt[Dot11EltHEOperation].six_g_op_info_present == 1 +assert pkt[Dot11HE6GOperationInfo].primary_channel == 5 +assert pkt[Dot11HE6GOperationInfo].channel_width == 2 +assert pkt[Dot11HE6GOperationInfo].channel_center0 == 37 +assert pkt[Dot11HE6GOperationInfo].channel_center1 == 0 +assert pkt[Dot11HE6GOperationInfo].minimum_rate == 12 +assert raw(pkt) == data + = Dot11EltOBSS in isolation pkt = Dot11EltOBSS(b'J\x0e\x14\x00\n\x00,\x01\xc8\x00\x14\x00\x05\x00\x19\x00') From 804c353101f25acb4f3d0ec6fa69687b75a08d55 Mon Sep 17 00:00:00 2001 From: Ugur Dogru Date: Tue, 30 Jun 2026 14:14:29 +0300 Subject: [PATCH 2/3] Removed 'notes' file. It's content is in PR notes already --- ...t11-extended-element-he-operation-notes.md | 46 ------------------- 1 file changed, 46 deletions(-) delete mode 100644 scapy/layers/0001-dot11-extended-element-he-operation-notes.md diff --git a/scapy/layers/0001-dot11-extended-element-he-operation-notes.md b/scapy/layers/0001-dot11-extended-element-he-operation-notes.md deleted file mode 100644 index e545c07ac2b..00000000000 --- a/scapy/layers/0001-dot11-extended-element-he-operation-notes.md +++ /dev/null @@ -1,46 +0,0 @@ -# PR 0001: Dot11 Element ID Extension and HE Operation - -Scope: - -- Adds `Dot11EltExtension` dispatch for Element ID `255`. -- Adds `Dot11EltExtensionGeneric` for unknown or unsupported extension IDs. -- Adds `Dot11EltHEOperation` for Extended ID `36`. -- Adds `Dot11HE6GOperationInfo` when the HE Operation element indicates that - 6 GHz operation information is present. -- Adds focused `test/scapy/layers/dot11.uts` tests for generic, short, HE, and - HE 6 GHz extended elements. - -Spec references used in code comments: - -- IEEE Std 802.11ax-2021, `9.4.2.1` and Table `9-92`: Element ID Extension. -- IEEE Std 802.11ax-2021, `9.4.2.249`: HE Operation element. -- IEEE Std 802.11ax-2021, Figure `9-788k`: 6 GHz Operation Information field. - -Intentional partial support: - -- Element ID Extension is a registry for many 802.11 information elements. This - patch does not try to implement all of them. -- Unknown and unsupported extension IDs intentionally remain - `Dot11EltExtensionGeneric`. -- EHT Operation and Multi-Link are left for separate follow-up patches so this - first PR is reviewable on its own. - -Focused test command used: - -```sh -test/run_tests -t test/scapy/layers/dot11.uts -n 57-61 -F -``` - -Result: - -```text -PASSED=5 FAILED=0 -``` - -Full dot11 suite note: - -- A full `test/scapy/layers/dot11.uts` run reached and passed the existing - tests up through `Dot11EltVHTOperation in isolation` before I interrupted it - in this local environment because it was taking longer than expected. - The focused tests for this patch passed. - From 401c67cca0091f10734716ea1216dc036ff8a7f6 Mon Sep 17 00:00:00 2001 From: Ugur Dogru Date: Thu, 2 Jul 2026 10:11:07 +0300 Subject: [PATCH 3/3] [802.11] Use dictionary to register extension tags --- scapy/layers/dot11.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/scapy/layers/dot11.py b/scapy/layers/dot11.py index 420e6b6647d..9c2722196ff 100644 --- a/scapy/layers/dot11.py +++ b/scapy/layers/dot11.py @@ -1578,11 +1578,25 @@ def dispatch_hook(cls, _pkt=None, *args, **kargs): if not _pkt or len(_pkt) < 3: return cls - length = orb(_pkt[1]) - ext_id = orb(_pkt[2]) - if ext_id == 36 and length >= 7: - return Dot11EltHEOperation - return Dot11EltExtensionGeneric + if _pkt: + ext_id = orb(_pkt[2]) + idcls = cls.registered_ext_ids.get(ext_id) + if idcls is not None: + return idcls + return Dot11EltExtensionGeneric + return cls + + registered_ext_ids = {} + + @classmethod + def register_variant(cls, ext_id=None): + ext_id = ext_id or cls.ext_ID.default + if not ext_id: + # This is Dot11EltExtension, register it in the super-class. + super().register_variant() + elif ext_id not in cls.registered_ext_ids: + # Register extension ID to class (e.g. Dot11EltHEOperation) + cls.registered_ext_ids[ext_id] = cls # 802.11ax-2021 9.4.2.1, Table 9-92 @@ -1668,6 +1682,9 @@ class Dot11EltHEOperation(Dot11EltExtension): ] +Dot11EltHEOperation.register_variant(36) + + ###################### # 802.11 Frame types # ######################