Repository: secdev/scapy

Issue:
NetFlow v9 dissection interferes with raw bytes
The [attached capture](https://github.com/secdev/scapy/files/3632876/v9_options-template-padding.pcap.gz) displays some strange behavior under Scapy 2.4.3 (on Linux x86-64, presumably elsewhere also).

Scapy and Wireshark disagree about what the contents of the UDP portion of the packet are:

```
Scapy:     fb200807007840a10009000277efe9c450c843f900362202000000000001001801000004000800010000002a0004002900040101004477ef819077ef81900000003c00000001009300930ac900640ac9033b060009ee0b3500000ac9033b131302000000000000260bdc69aa6480996649a0000000000000
Wireshark: fb200807007840a10009000277efe9c450c843f900362202000000000001001801000004000800010000002a00040029000400000101004477ef819077ef81900000003c00000001009300930ac900640ac9033b060009ee0b3500000ac9033b131302000000000000260bdc69aa6480996649a000000000
```

Looking at the actual PCAP data with a hex editor supports the Wireshark interpretation.

Scapy seems to be dropping a `00 00` and tacking it on to the end. The missing pair of bytes are interpreted correctly in Wireshark as NetFlow padding bytes:

![image](https://user-images.githubusercontent.com/1922815/65275751-e70bf100-daeb-11e9-81e1-d6186f99979f.png)

This script shows the issue:
```python
from binascii import hexlify
from scapy.all import PcapReader

with PcapReader('v9_options-template-padding.pcap.gz') as reader:
    packet = next(reader)

hexlify(bytes(packet['UDP'])).decode()
```

Doing this (i.e., turning off the NetFlow dissector restores the correct behavior) mitigates the issue:

```python
split_layers(UDP, NetflowHeader, dport=2055)
split_layers(UDP, NetflowHeader, sport=2055)
```

So the problem is that the NetFlow dissector is interfering with how the earlier parts of the packet are represented.


Discussion:
I noticed the NetflowDataflowsetV9 is parsed as a payload of NetflowOptionsFlowsetV9, but actually it should be under NetflowHeaderV9 and in parallel to NetflowOptionsFlowsetV9. 
So I think it's a bug of scapy's netflow implementation
This is happening because NetflowDataflowsetV9 is binded as payload of NetflowDataflowsetV9. It should be like this:

```
Netflow Header
    NetflowOptionsFlowsetV9
      templates=[]
      padding zero
   NetflowDataflowsetV9
      datafields
```

But scapy parsed as:

```
Netflow Header
    NetflowOptionsFlowsetV9
      templates=[]
      NetflowDataflowsetV9
          datafields
      padding zero

```


Resolving patch:
diff --git a/scapy/layers/netflow.py b/scapy/layers/netflow.py
--- a/scapy/layers/netflow.py
+++ b/scapy/layers/netflow.py
@@ -35,7 +35,8 @@
     FlagsField, IPField, IntField, MACField, \
     PacketListField, PadField, SecondsIntField, ShortEnumField, ShortField, \
     StrField, StrFixedLenField, ThreeBytesField, UTCTimeField, XByteField, \
-    XShortField, LongField, BitField, ConditionalField, BitEnumField
+    XShortField, LongField, BitField, ConditionalField, BitEnumField, \
+    StrLenField
 from scapy.packet import Packet, bind_layers, bind_bottom_up
 from scapy.plist import PacketList
 from scapy.sessions import IPSession, DefaultSession
@@ -1594,24 +1595,21 @@ class NetflowOptionsFlowsetV9(Packet):
                    PacketListField(
                        "options", [],
                        NetflowOptionsFlowsetOptionV9,
-                       length_from=lambda pkt: pkt.option_field_length)]
-
-    def extract_padding(self, s):
-        if self.length is None:
-            return s, ""
-        # Calc pad length
-        pad_len = self.length - self.option_scope_length - \
-            self.option_field_length - 10
-        return s[pad_len:], s[:pad_len]
+                       length_from=lambda pkt: pkt.option_field_length),
+                   StrLenField("pad", None, length_from=lambda pkt: (
+                       pkt.length - pkt.option_scope_length -
+                       pkt.option_field_length - 10))]
 
     def default_payload_class(self, p):
         return conf.padding_layer
 
     def post_build(self, pkt, pay):
-        # Padding 4-bytes with b"\x00"
-        pkt += (-len(pkt) % 4) * b"\x00"
         if self.length is None:
             pkt = pkt[:2] + struct.pack("!H", len(pkt)) + pkt[4:]
+        if self.pad is None:
+            # Padding 4-bytes with b"\x00"
+            start = 10 + self.option_scope_length + self.option_field_length
+            pkt = pkt[:start] + (-len(pkt) % 4) * b"\x00"
         return pkt + pay
 
 
@@ -1642,14 +1640,18 @@ class NetflowOptionsFlowset10(NetflowOptionsFlowsetV9):
                        NetflowOptionsFlowsetOptionV9,
                        count_from=lambda pkt: (
                            pkt.field_count - pkt.scope_field_count
-                       ))]
+                       )),
+                   StrLenField("pad", None, length_from=lambda pkt: (
+                       pkt.length - (pkt.scope_field_count * 4) - 10))]
 
-    def extract_padding(self, s):
+    def post_build(self, pkt, pay):
         if self.length is None:
-            return s, ""
-        # Calc pad length
-        pad_len = self.length - (self.scope_field_count * 4) - 10
-        return s[pad_len:], s[:pad_len]
+            pkt = pkt[:2] + struct.pack("!H", len(pkt)) + pkt[4:]
+        if self.pad is None:
+            # Padding 4-bytes with b"\x00"
+            start = 10 + self.scope_field_count * 4
+            pkt = pkt[:start] + (-len(pkt) % 4) * b"\x00"
+        return pkt + pay
 
 
 bind_layers(NetflowHeader, NetflowHeaderV9, version=9)


Test patch:
diff --git a/test/regression.uts b/test/regression.uts
--- a/test/regression.uts
+++ b/test/regression.uts
@@ -6767,6 +6767,14 @@ assert pkt.records[0].OUTPUT_SNMP == b'\x00\x00\x02\xfb'
 
 assert raw(pkt) == b'\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x08\x00E\x00\x00\xcc\x00\x01\x00\x00@\x11|\x1e\x7f\x00\x00\x01\x7f\x00\x00\x01\x08\x07\x08\x07\x00\xb8\x86\xe7\x00\t\x00\x02\x00\x00\x00\x00\\C\x7f5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\\\x01\xa8\x00\x15\x00\x08\x00\x04\x00\x0c\x00\x04\x00\x05\x00\x01\x00\x04\x00\x01\x00\x07\x00\x02\x00\x0b\x00\x02\x00 \x00\x02\x00\n\x00\x04\x00\x10\x00\x04\x00\x11\x00\x04\x00\x12\x00\x04\x00\x0e\x00\x04\x00\x01\x00\x04\x00\x02\x00\x04\x00\x16\x00\x04\x00\x15\x00\x04\x00\x0f\x00\x04\x00\t\x00\x01\x00\r\x00\x01\x00\x06\x00\x01\x00<\x00\x01\x01\xa8\x00@\x14\x00\x00\xfd\x1e\x00\x00\xfd\x00\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x03 \x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x02\xfb\x00\x15a|\x00\x00\x07\x0f$\x95x\xed$\x99\x91<\ndg\x01  \x00\x04'
 
+= NetflowV9 - padding #GH2257
+
+dat = hex_bytes("fb200807007840a10009000277efe9c450c843f900362202000000000001001801000004000800010000002a00040029000400000101004477ef819077ef81900000003c00000001009300930ac900640ac9033b060009ee0b3500000ac9033b131302000000000000260bdc69aa6480996649a000000000")
+pkt = UDP(dat)
+assert pkt[NetflowOptionsFlowsetV9].pad == b"\x00\x00"
+pkt[NetflowOptionsFlowsetV9].pad = None
+assert raw(pkt) == dat
+
 
 ############
 ############