Repository: secdev/scapy

Issue:
Some 802.11 Control Frames do not have the correct number of address fields in their MAC header.
### Brief description

802.11 Control frame (type 1) subtypes 4, 5, and 6 (Beam Forming Report Poll, VHT NDP Announcement, and Control Frame Extension) are not generated with the correct number of address fields in their MAC header.  These subtypes (per the 802.11 2016 specification) should all have both Address 1 and  Address 2 fields present.  For the case of the DMG DTS control frame (type 1, subtype 6, control frame extension 6) Address 1, Address 2, and Address 3 fields should be present.  Currently SCAPY generates all of these frame types with only the Address 1 field present.

The script to generate this issue, its output to console (show()), and the generated pcap file are attached in a zip file.
[missing_addr.zip](https://github.com/secdev/scapy/files/10143942/missing_addr.zip)


### Scapy version

2.4.5

### Python version

3.8.10

### Operating system

Ubuntu 20.04.5 LTS

### Additional environment information

_No response_

### How to reproduce

Run the following script to produce the issue (python3 -c “import missing_addr; missing_addr.missing_addr()”).  This script generates "Beam Forming Report Poll" and "VHT NDP Announcement" control frames.  Although the addr2 field is supplied, the output pcap files are malformed because of the missing addr2 field.

![image](https://user-images.githubusercontent.com/118848041/205374874-ef36ff04-7bc4-4ed5-a79f-664fccafd824.png)


### Actual result

The output of the pkt.show() in the missing_addr.py script outputs the "Beam Forming Report Poll" and "VHT NDP Announcement" control frames generated without addr2 even though it was supplied to Dot11.

![image](https://user-images.githubusercontent.com/118848041/205375722-d2ede8be-e8a4-454b-9794-ae996e84efc4.png)

The pcap file confirms that the frames are malformed and missing addr2.

![image](https://user-images.githubusercontent.com/118848041/205375927-c1ab11da-57c3-4131-9eea-77a9e4aeb6e8.png)


### Expected result

802.11 Control frame (type 1) subtypes 4, 5, and 6 (Beam Forming Report Poll, VHT NDP Announcement, and Control Frame Extension) should all have both Address 1 and  Address 2 fields present.  For the case of the DMG DTS control frame (type 1, subtype 6, control frame extension 6) Address 1, Address 2, and Address 3 fields should be present. 

The issue seems to be in the SCAPY Dot11 class between lines 711 and 727.  Lines 715-716 limits the Address 2 field to packets in type 1 (control frame) subtype 8,9,10,11,14, and 15 - the subtypes should be expanded to include subtypes 4, 5, and 6.  Line 720 limits the address 3 field to types 0 and 2 – this should be expanded to include type 1, subtype 6, control frame extension 6 (DMG DTS).

![image](https://user-images.githubusercontent.com/118848041/205376896-6c5cd4bd-d6d9-4d86-b791-1791cae62d2f.png)




### Related resources

Here are the 802.11 2016 references to the frame format and associated address fields for the control frames mentioned above.

![image](https://user-images.githubusercontent.com/118848041/205376579-14bb9f6b-0092-480e-8d62-321e9cf8f235.png)



Discussion:
You've just solved or explain a related "bug" for me @wfast01 -- https://github.com/secdev/scapy/issues/3793.  Good job!  Curious where this goes.

Highly important these changes follow IEEE specs to a T in these regards.
@wfast01 Are you going to submit a PR?
Yes.  It may take a few days due to my end-of-the-year workload.   Currently I am putting together a test harness that generates each of the 802.11 2016 frames listed in Table 9-1 so I can verify the number of address fields, their content, and their placement in the MAC header.

Resolving patch:
diff --git a/scapy/layers/dot11.py b/scapy/layers/dot11.py
--- a/scapy/layers/dot11.py
+++ b/scapy/layers/dot11.py
@@ -713,11 +713,12 @@ class Dot11(Packet):
         ConditionalField(
             _Dot11MacField("addr2", ETHER_ANY, 2),
             lambda pkt: (pkt.type != 1 or
-                         pkt.subtype in [0x8, 0x9, 0xa, 0xb, 0xe, 0xf]),
+                         pkt.subtype in [0x4, 0x5, 0x6, 0x8, 0x9, 0xa, 0xb, 0xe, 0xf]),
         ),
         ConditionalField(
             _Dot11MacField("addr3", ETHER_ANY, 3),
-            lambda pkt: pkt.type in [0, 2],
+            lambda pkt: (pkt.type in [0, 2] or
+                         ((pkt.type, pkt.subtype) == (1, 6) and pkt.cfe == 6)),
         ),
         ConditionalField(LEShortField("SC", 0), lambda pkt: pkt.type != 1),
         ConditionalField(
@@ -770,6 +771,8 @@ def address_meaning(self, index):
         if self.type == 0:  # Management
             return _dot11_addr_meaning[0][index]
         elif self.type == 1:  # Control
+            if (self.type, self.subtype) == (1, 6) and self.cfe == 6:
+                return ["RA", "NAV-SA", "NAV-DA"][index]
             return _dot11_addr_meaning[1][index]
         elif self.type == 2:  # Data
             meaning = _dot11_addr_meaning[2][index][


Test patch:
diff --git a/test/scapy/layers/dot11.uts b/test/scapy/layers/dot11.uts
--- a/test/scapy/layers/dot11.uts
+++ b/test/scapy/layers/dot11.uts
@@ -61,6 +61,18 @@ assert Dot11Elt(info="scapy").summary() == "SSID='scapy'"
 assert Dot11Elt(ID=1).mysummary() == ""
 assert Dot11(b'\x84\x00\x00\x00\x00\x11\x22\x33\x44\x55\x00\x11\x22\x33\x44\x55').addr2 == '00:11:22:33:44:55'
 
+= Dot11 - type 1 subtype 4, 5, 6
+
+assert raw(Dot11(type=1, subtype=4, addr2="ff:ff:ff:ff:ff:ff")) == b'D\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff'
+assert raw(Dot11(type=1, subtype=5, addr2="ff:ff:ff:ff:ff:ff")) == b'T\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff'
+assert raw(Dot11(type=1, subtype=6, addr2="ff:ff:ff:ff:ff:ff", cfe=3)) == b'd0\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff'
+assert raw(Dot11(type=1, subtype=6, addr2="ff:ff:ff:ff:ff:ff", cfe=6, addr3="aa:aa:aa:aa:aa:aa")) == b'd`\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xaa\xaa\xaa\xaa\xaa\xaa'
+
+assert Dot11(type=1, subtype=5).address_meaning(1) == 'RA'
+assert Dot11(type=1, subtype=6, cfe=5).address_meaning(2) == 'TA'
+assert Dot11(type=1, subtype=6, cfe=6).address_meaning(2) == 'NAV-SA'
+assert Dot11(type=1, subtype=6, cfe=6).address_meaning(3) == 'NAV-DA'
+
 = Multiple Dot11Elt layers
 pkt = Dot11() / Dot11Beacon() / Dot11Elt(ID="Supported Rates") / Dot11Elt(ID="SSID", info="Scapy")
 assert pkt[Dot11Elt::{"ID": 0}].info == b"Scapy"