Repository: secdev/scapy

Issue:
ICMPv6 lacks support for MLDv2
__affected version:__ master

__affected protocol(s):__ ~IPv6~, ICMPv6

__brief description__: ~fails to dissect payload after IPv6 extension header (probably related to padding with length of zero)~ as [discovered](https://github.com/secdev/scapy/issues/1152#issuecomment-365935428) by  @guedou MLDv2 is not supported by the ICMPv6 layer

__how to reproduce__:

(using attached trace)
```
frm = rdpcap('ipv6-with-extension-and-zero-padding.pcap', count=1)
frm.show()
```

__actual result__:

```
0000 Ether / fe80::a00:27ff:fed4:10bb > ff02::16 (0) / IPv6ExtHdrHopByHop / Raw
```

Payload after _IPv6ExtHdrHopByHop_ is dissected as Raw().

__expected result__:

as seen in Wireshark:
![ipv6-extension-with-zero-padding](https://user-images.githubusercontent.com/1612957/36247949-188526dc-1236-11e8-80e0-4c00a0c61e94.png)


__sample trace:__ [ipv6-with-extension-and-zero-padding.zip](https://github.com/secdev/scapy/files/1726867/ipv6-with-extension-and-zero-padding.zip)



Discussion:
Scapy does not currently support MLDv2. That the real issue here.
is see...

for reference: https://tools.ietf.org/html/rfc3810
I am working on a fix.

Resolving patch:
diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py
--- a/scapy/layers/inet6.py
+++ b/scapy/layers/inet6.py
@@ -398,7 +398,10 @@ def default_payload_class(self,p):
             if len(p) > 2 and (t == 139 or t == 140): # Node Info Query
                 return _niquery_guesser(p)
             if len(p) >= icmp6typesminhdrlen.get(t, float("inf")): # Other ICMPv6 messages
-                return get_cls(icmp6typescls.get(t,"Raw"), "Raw")
+                if t == 130 and len(p) >= 28:
+                    # RFC 3810 - 8.1. Query Version Distinctions
+                    return ICMPv6MLQuery2
+                return get_cls(icmp6typescls.get(t, "Raw"), "Raw")
             return Raw
         elif self.nh == 135 and len(p) > 3: # Mobile IPv6
             return _mip6_mhtype2cls.get(orb(p[2]), MIP6MH_Generic)
@@ -536,7 +539,7 @@ def answers(self, other):
             # a specific task. Currently, don't see any use ...
             return self.payload.payload.answers(other)
         elif other.nh == 0 and isinstance(other.payload, IPv6ExtHdrHopByHop):
-            return self.payload.answers(other.payload.payload)
+            return self.payload.answers(other.payload)
         elif other.nh == 44 and isinstance(other.payload, IPv6ExtHdrFragment):
             return self.payload.answers(other.payload.payload)
         elif other.nh == 43 and isinstance(other.payload, IPv6ExtHdrRouting):
@@ -1309,7 +1312,7 @@ def fragment6(pkt, fragSize):
                      4: "ICMPv6ParamProblem",
                    128: "ICMPv6EchoRequest",
                    129: "ICMPv6EchoReply",
-                   130: "ICMPv6MLQuery",
+                   130: "ICMPv6MLQuery",  # MLDv1 or MLDv2
                    131: "ICMPv6MLReport",
                    132: "ICMPv6MLDone",
                    133: "ICMPv6ND_RS",
@@ -1322,7 +1325,7 @@ def fragment6(pkt, fragSize):
                    140: "ICMPv6NIReply",
                    141: "ICMPv6ND_INDSol",
                    142: "ICMPv6ND_INDAdv",
-                  #143: Do Me - RFC 3810
+                   143: "ICMPv6MLReport2",
                    144: "ICMPv6HAADRequest",
                    145: "ICMPv6HAADReply",
                    146: "ICMPv6MPSol",
@@ -1352,6 +1355,7 @@ def fragment6(pkt, fragSize):
                          #140
                          141: 8,
                          142: 8,
+                         143: 8,
                          144: 8,
                          145: 8,
                          146: 8,
@@ -1382,7 +1386,7 @@ def fragment6(pkt, fragSize):
              140 : "ICMP Node Information Response",
              141 : "Inverse Neighbor Discovery Solicitation Message",
              142 : "Inverse Neighbor Discovery Advertisement Message",
-             143 : "Version 2 Multicast Listener Report",
+             143 : "MLD Report Version 2",
              144 : "Home Agent Address Discovery Request Message",
              145 : "Home Agent Address Discovery Reply Message",
              146 : "Mobile Prefix Solicitation",
@@ -1499,7 +1503,7 @@ def answers(self, other):
                 self.data == other.data)
 
 
-############ ICMPv6 Multicast Listener Discovery (RFC3810) ##################
+############ ICMPv6 Multicast Listener Discovery (RFC2710) ##################
 
 # tous les messages MLD sont emis avec une adresse source lien-locale
 # -> Y veiller dans le post_build si aucune n'est specifiee
@@ -1528,14 +1532,7 @@ class ICMPv6MLQuery(_ICMPv6ML): # RFC 2710
     type   = 130
     mrd    = 10000 # 10s for mrd
     mladdr = "::"
-    overload_fields = {IPv6: { "dst": "ff02::1", "hlim": 1, "nh": 58 }}
-    def hashret(self):
-        if self.mladdr != "::":
-            return (
-                inet_pton(socket.AF_INET6, self.mladdr) + self.payload.hashret()
-            )
-        else:
-            return self.payload.hashret()
+    overload_fields = {IPv6: { "dst": "ff02::1", "hlim": 1, "nh": 58}}
 
 
 # TODO : See what we can do to automatically include a Router Alert
@@ -1544,7 +1541,10 @@ class ICMPv6MLReport(_ICMPv6ML): # RFC 2710
     name = "MLD - Multicast Listener Report"
     type = 131
     overload_fields = {IPv6: {"hlim": 1, "nh": 58}}
-    # implementer le hashret et le answers
+
+    def answers(self, query):
+        """Check the query type"""
+        return ICMPv6MLQuery in query
 
 # When a node ceases to listen to a multicast address on an interface,
 # it SHOULD send a single Done message to the link-scope all-routers
@@ -1558,6 +1558,80 @@ class ICMPv6MLDone(_ICMPv6ML): # RFC 2710
     overload_fields = {IPv6: { "dst": "ff02::2", "hlim": 1, "nh": 58}}
 
 
+############ Multicast Listener Discovery Version 2 (MLDv2) (RFC3810) #######
+
+class ICMPv6MLQuery2(_ICMPv6): # RFC 3810
+    name = "MLDv2 - Multicast Listener Query"
+    fields_desc = [ ByteEnumField("type", 130, icmp6types),
+                    ByteField("code", 0),
+                    XShortField("cksum", None),
+                    ShortField("mrd", 10000),
+                    ShortField("reserved", 0),
+                    IP6Field("mladdr","::"),
+                    BitField("Resv", 0, 4),
+                    BitField("S", 0, 1),
+                    BitField("QRV", 0, 3),
+                    ByteField("QQIC", 0),
+                    ShortField("sources_number", None),
+                    IP6ListField("sources", [],
+                                 count_from=lambda pkt: pkt.sources_number) ]
+
+    # RFC8810 - 4. Message Formats
+    overload_fields = {IPv6: {"dst": "ff02::1", "hlim": 1 , "nh": 58}} 
+
+    def post_build(self, packet, payload):
+        """Compute the 'sources_number' field when needed"""
+        if self.sources_number is None:
+            srcnum = struct.pack("!H", len(self.sources))
+            packet = packet[:26] + srcnum + packet[28:]
+        return _ICMPv6.post_build(self, packet, payload)
+
+
+class ICMPv6MLDMultAddrRec(Packet):
+    name = "ICMPv6 MLDv2 - Multicast Address Record"
+    fields_desc = [ ByteField("rtype", 4), 
+                    FieldLenField("auxdata_len", None,
+                                  length_of="auxdata"),
+                    FieldLenField("sources_number", None,
+                                  length_of="sources",
+                                  adjust=lambda p,num: num//16),
+                    IP6Field("dst", "::"),
+                    IP6ListField("sources", [],
+                                 length_from=lambda p: 16*p.sources_number),
+                     StrLenField("auxdata", "",
+                                 length_from=lambda p: p.auxdata_len) ]
+
+    def default_payload_class(self, packet):
+        """Multicast Address Record followed by another one"""
+        return self.__class__
+
+
+class ICMPv6MLReport2(_ICMPv6): # RFC 3810
+    name = "MLDv2 - Multicast Listener Report"
+    fields_desc = [ ByteEnumField("type", 143, icmp6types),
+                    ByteField("res", 0),
+                    XShortField("cksum", None),
+                    ShortField("reserved", 0),
+                    ShortField("records_number", None),
+                    PacketListField("records", [],
+                                    ICMPv6MLDMultAddrRec,
+                                    count_from=lambda p: p.records_number) ]
+
+    # RFC8810 - 4. Message Formats
+    overload_fields = {IPv6: {"dst": "ff02::16", "hlim": 1 , "nh": 58}}
+
+    def post_build(self, packet, payload):
+        """Compute the 'records_number' field when needed"""
+        if self.records_number is None:
+            recnum = struct.pack("!H", len(self.records))
+            packet = packet[:6] + recnum + packet[8:]
+        return _ICMPv6.post_build(self, packet, payload)
+
+    def answers(self, query):
+        """Check the query type"""
+        return isinstance(query, ICMPv6MLQuery2)
+
+
 ########## ICMPv6 MRD - Multicast Router Discovery (RFC 4286) ###############
 
 # TODO:


Test patch:
diff --git a/test/regression.uts b/test/regression.uts
--- a/test/regression.uts
+++ b/test/regression.uts
@@ -3494,11 +3494,45 @@ assert _ICMPv6Error().guess_payload_class(None) == IPerror6
 + ICMPv6ML
 
 = ICMPv6MLQuery - build & dissection
-s = raw(IPv6()/ICMPv6MLQuery())
-s == b"`\x00\x00\x00\x00\x18:\x01\xfe\x80\x00\x00\x00\x00\x00\x00\xba\xca:\xff\xfer\xb0\x8b\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x82\x00\xb4O'\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+s = raw(IPv6(src="fe80::1")/ICMPv6MLQuery())
+assert s == b"`\x00\x00\x00\x00\x18:\x01\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x82\x00Y\x17'\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
 
 p = IPv6(s)
-ICMPv6MLQuery in p and p[IPv6].dst == "ff02::1"
+assert ICMPv6MLQuery in p and p[IPv6].dst == "ff02::1"
+
+= Check awnswers
+
+q = IPv6()/IPv6ExtHdrHopByHop(options=[RouterAlert()])/ICMPv6MLQuery()
+a = IPv6()/IPv6ExtHdrHopByHop(options=[RouterAlert()])/ICMPv6MLReport()
+
+assert a.answers(q)
+
+############
+############
++ ICMPv6MLv2
+
+= ICMPv6MLQuery2 - build & dissection
+p = IPv6()/IPv6ExtHdrHopByHop(options=[RouterAlert()])/ICMPv6MLQuery2(sources=["::1"])
+s = raw(p)
+assert s == b"`\x00\x00\x00\x004\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01:\x00\x05\x02\x00\x00\x01\x00\x82\x00V\x85'\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
+
+p = IPv6(s)
+assert ICMPv6MLQuery2 in p and p.sources_number == 1
+
+= ICMPv6MLReport2 - build & dissection
+p = IPv6()/IPv6ExtHdrHopByHop(options=[RouterAlert()])/ICMPv6MLReport2(records=[ICMPv6MLDMultAddrRec(), ICMPv6MLDMultAddrRec(sources=["::1"], auxdata="scapy")])
+s = raw(p)
+assert s == b'`\x00\x00\x00\x00O\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01:\x00\x05\x02\x00\x00\x01\x00\x8f\x00\x1e\x9b\x00\x00\x00\x02\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x05\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01scapy'
+
+p = IPv6(s)
+assert ICMPv6MLReport2 in p and p.records_number == 2
+
+= Check awnswers
+
+q = IPv6()/IPv6ExtHdrHopByHop(options=[RouterAlert()])/ICMPv6MLQuery2()
+a = IPv6()/IPv6ExtHdrHopByHop(options=[RouterAlert()])/ICMPv6MLReport2()
+
+assert a.answers(q)
 
 ############
 ############