diff --git a/scapy/layers/igmp.py b/scapy/layers/igmp.py index 711e4fa03ab..f418ce906db 100644 --- a/scapy/layers/igmp.py +++ b/scapy/layers/igmp.py @@ -159,6 +159,10 @@ def dispatch_hook(cls, _pkt=None, *args, **kargs): elif typ == 0x32: return IGMPv3_MRT + # IGMPv3() has no raw bytes yet. Keep that class; falling through to + # IGMP_MQ made the pre-2.8 stack IGMPv3()/IGMPv3mr() a v1/v2 query. + if issubclass(cls, IGMPv3): + return cls return IGMP_MQ def answers(self, other): @@ -252,6 +256,41 @@ def mysummary(self): else: return self.sprintf(f"IGMPv3 %{t}.type%") + def _stacked_full_message(self, payload): + """True when payload is already a complete IGMPv3 message. + + contrib.igmpv3 aliases IGMPv3mr to IGMPv3_MR. Code written for 2.7 + still does IGMPv3()/IGMPv3mr(), which used to be one report. IGMPv3_MR + already includes the header, so keep that message and drop this one. + """ + return ( + type(self) is IGMPv3 + and isinstance(payload, IGMPv3) + and type(payload) is not IGMPv3 + ) + + def add_payload(self, payload): + # IP()/IGMPv3()/IGMPv3_MR() lands here: IP.add_payload delegates to us. + if self._stacked_full_message(payload) and self.underlayer is not None: + under = self.underlayer + under.remove_payload() + under.add_payload(payload) + return + super(IGMPv3, self).add_payload(payload) + + def __div__(self, other): + if isinstance(other, Packet) and self._stacked_full_message(other): + other = other.copy() + if self.underlayer is not None: + under = self.underlayer.copy() + under.remove_payload() + under.add_payload(other) + return under + return other + return super(IGMPv3, self).__div__(other) + + __truediv__ = __div__ + class IGMPv3_MQ(IGMPv3): """ diff --git a/test/scapy/layers/igmp.uts b/test/scapy/layers/igmp.uts index 601417e197c..2c58f649955 100644 --- a/test/scapy/layers/igmp.uts +++ b/test/scapy/layers/igmp.uts @@ -121,3 +121,17 @@ assert isinstance(IGMP(raw(IGMPv3_MQ())), IGMPv3) pkt = IGMPv3_MR(records=[IGMPv3_MR_Group(maddr="127.0.0.1")]) assert pkt.summary() == 'IGMPv3 Version 3 - Membership Report' + += IGMPv3 - legacy contrib stack is one report + +load_contrib("igmpv3") +from scapy.contrib.igmpv3 import IGMPv3mr, IGMPv3gr +group = "232.1.1.12" +sources = ["10.0.0.1", "10.0.0.2"] +legacy = IGMPv3()/IGMPv3mr(records=[IGMPv3gr(rtype=1, maddr=group, srcaddrs=sources)]) +current = IGMPv3_MR(records=[IGMPv3_MR_Group(rtype=1, maddr=group, srcaddrs=sources)]) +assert bytes(legacy) == bytes(current) +assert bytes(legacy)[0] == 0x22 +legacy_ip = IP(src="192.168.10.2", dst="224.0.0.22")/IGMPv3()/IGMPv3mr(records=[IGMPv3gr(rtype=1, maddr=group, srcaddrs=sources)]) +assert bytes(legacy_ip[IGMPv3]) == bytes(current) +assert IGMPv3_MR in legacy_ip