Repository: secdev/scapy

Issue:
Python recursion limit reached with RTR packet
If I dissect the RTR packet included in the following code, I get the following error : RuntimeError: maximum recursion depth exceeded while getting the str of an object

#### Environment

-   Scapy version: 2.4.0.dev592
-   Python version: Python 3.6.7
-   Operating System: Debian testing

#### How to reproduce

from scapy.all import *
from scapy.contrib import rtr
import sys
import binascii

conf.debug_dissector = True
data = binascii.unhexlify('00163ea9041a8478ac5b82c2080045000b84b9a3400035069f278d161cdcb91a7e9c205acba20a4627104ae28cc0801000e3ed1f00000101080aea589f8281fbc40a0003132f0000000800040000000000140113130059b9e0000000618b000400000000001401181800b4ea51000000b37f0006000000000020012020002a03cd8000000000000000000000000000003fe2000400000000001401101800a1ea0000000018a200040000000000140118180055bbf300000073fe000400000000001401101800c10a000000000675000400000000001401161600b96740000000728e000400000000001401151500259830000000e2330004000000000014011313004dde8000000052e3000400000000001401181800c870e000000034450004000000000014011818002d70b200000027040004000000000014011818007859790000005cc800040000000000140113130070c8600000002453000400000000001401181800b908d20000030b8f0004000000000014011515008623180000007899000400000000001401141500bf6600000004004c00040000000000140116160025e7a4000000b9e50004000000000014011717004d2fb8000000639c000400000000001401181800af6be50000005f780004000000000014011717007c1dc80000005f780006000000000020013030002a077b800106000000000000000000000000b9dd0004000000000014011616007086040000002471000400000000001401181800b91cdc000000abdc00060000000000200128280024001240c100000000000000000000000000fa62000600000000002001282800240012404100000000000000000000000000fa62000400000000001401161600b99124000000ef8500040000000000140118180073ba960000005c7a00040000000000140118180005391400000315d4000400000000001401181800c807d30000006d2b0004000000000014011818002d045900000409dc00040000000000140115150025ecb0000000c616000400000000001401131800be0c2000000058c40004000000000014011718005bc2d0000000201a00040000000000140116160070c96800000024530004000000000014010e1900182c0000000017f0000400000000001401141400515e7000000085f000040000000000140118180048ff3c000000254500040000000000140112180025ba40000000c288000400000000001401181800b9a93400000314fa000400000000001401181800c22d5e000000a95a0004000000000014011618009d64780000006d2b0004000000000014011717007c1dcc0000005f780004000000000014011717004e85aa000000c84d000400000000001401131300b00a0000000087870004000000000014011818009effd4000000df51000400000000001401181800c18d3d0000000cf8000400000000001401181800053f0c000000e099000400000000001401131300b4be2000000204670004000000000014011515004e1c20000000af72000400000000001401181800c37284000000200f000400000000001401181800be7cfa0000006dce000400000000001401181800c116a900000085a100040000000000140116160025bf44000000bcb50006000000000020011d1d002a00ae00000000000000000000000000000085d700040000000000140111110055930000000023b700060000000000200128280024001240c20000000000000000000000000212d000060000000000200128280024001240420000000000000000000000000212d00004000000000014010e0e0054d80000000008470004000000000014011818007aff030000004651000400000000001401131800d58d00000000327e000400000000001401161600d5a0f80000001aae000400000000001401181800d59f7800000033f6000400000000001401181800c2c75f0000000897000400000000001401181800bcd7f70000007b96000400000000001401181800b9c61f00000324a20004000000000014011515005ec6980000000474000400000000001401161600b993dc000000a73b0006000000000020013030002a04e4c000100000000000000000000000008f54000400000000001401181800b979f100000020af0004000000000014011313003a41800000005c7a000600000000002001282800240012409e0000000000000000000000000212d0000600000000002001282800240012401e0000000000000000000000000212d0000400000000001401181800bc725b000000aaee000400000000001401161600d990280000003fe40004000000000014011818005b8e5a000000a2fa00040000000000140117170054022e0000003cb90004000000000014011717005cff46000000a50d0004000000000014011717005362dc00000078960004000000000014011818005fd5c0000000c16100040000000000140112120051d2c00000001aae000600000000002001203000280003400000000000000000000000000000163c00060000000000200120300020010630000000000000000000000000000003120004000000000014011818005e4f5c000000bb0b0004000000000014011818002bf58f000000e55d000400000000001401171800505f160000003f1a000400000000001401181800c2926900000021e2000400000000001401141400d9a8200000001aae000400000000001401131800be34000000000ee80004000000000014011212003e41400000000847000400000000001401181800b043d3000000acdc000400000000001401181800b9ad7e000000c59700060000000000200127270024001240c60000000000000000000000000212d000060000000000200127270024001240460000000000000000000000000212d0000400000000001401161600b91f2c00000015980004000000000014011818002bfaf2000000b12c000400000000001401181800b90e180000030a68000400000000001401181800b4ea45000000b37f000400000000001401181800c23ac300000021e20004000000000014011818002e21a1000000c59700040000000000140116160031f4e40000005cc800060000000000200120200028032540000000000000000000000000000409dc0004000000000014011818002e21a6000000c5970006000000000020012030002a0217e8000000000000000000000000000032ca00040000000000140118180071c7de0000005cc800040000000000140118180097ed3d0000007a370004000000000014011818005362940000007896000400000000001401161600b9c610000000df830004000000000014011818007a9be2000000122b0004000000000014011515001f1d30000000bcb50004000000000014011313006e3680000000b26000040000000000140118180097ea8a000000ca74000400000000001401181800258f5b0000002097000400000000001401161600b988d4000000510f0004000000000014011818007117cd000000952600040000000000140117170058c75000000319ff0004000000000014011313004f87c0000000ac2e000400000000001401181800b94395000000e004000400000000001401141800b7b160000000e447000400000000001401171800c138ec00000062620006000000000020012020002803a44000000000000000000000000000040660000400000000001401181800b93f5c00000311e400040000000000140117170071c78a0000005cc800040000000000140116160067d64c000002109f00040000000000140118180071c7fc0000005cc8000400000000001401181800d41e39000000bb3f000400000000001401141400d93c400000007b3d000400000000001401101800745800000000276b000400000000001401131300d50d200000003ca50004000000000014011818005fadba000000c967000400000000001401161600d5cf380000009abb000400000000001401171700caa35c0000005f78000400000000001401101000c2a40000000022c100040000000000140117170054012e000000acd7')

print(Ether(data).summary())

#### Actual result

[...]
  File "/usr/lib/python2.7/logging/__init__.py", line 1292, in _log
    record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra)
  File "/usr/lib/python2.7/logging/__init__.py", line 1266, in makeRecord
    rv = LogRecord(name, level, fn, lno, msg, args, exc_info, func)
RuntimeError: maximum recursion depth exceeded

#### Expected result

Ether / IP / TCP 141.22.28.220:rtr > 185.26.126.156:52130 A / RTRHeader / RTRCacheResponse / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv6Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix / RTRHeader / RTRIPv4Prefix

#### Workaround

import sys
sys.setrecursionlimit(2200)


Discussion:
@gpotter2 @p-l- I have been trying to debug this, and I wonder if we can recover from the RuntimeError as this is not a real recursion.
It’s quite easy to come up with a workaround, but that would require to fix multiple functions the same way (`__repr__`, or `__getattr__`) for instance :/

I looked up a bit and it couldn’t find a way to bypass the error. We can always catch it but calling anything more below will trigger it again

https://pastebin.com/y4Gp8T3h

![29bfece8-5e17-4eb2-93a7-c60681696e39](https://user-images.githubusercontent.com/10530980/49613419-db5d9c80-f9a7-11e8-9307-f36bd30b28e1.jpeg)


Resolving patch:
diff --git a/scapy/contrib/rtr.py b/scapy/contrib/rtr.py
--- a/scapy/contrib/rtr.py
+++ b/scapy/contrib/rtr.py
@@ -24,12 +24,13 @@
 
 import struct
 
-from scapy.packet import Packet, bind_layers
+from scapy.packet import Packet, bind_layers, Raw
 from scapy.fields import ByteEnumField, ByteField, IntField, ShortField
 from scapy.fields import IPField, IP6Field, StrLenField
 from scapy.fields import FieldLenField
 from scapy.fields import StrFixedLenField, ShortEnumField
 from scapy.layers.inet import TCP
+from scapy.compat import orb
 
 STATIC_SERIAL_NOTIFY_LENGTH = 12
 STATIC_SERIAL_QUERY_LENGTH = 12
@@ -77,7 +78,9 @@ class RTRSerialNotify(Packet):
     '''
 
     name = 'Serial Notify'
-    fields_desc = [ShortField('session_id', 0),
+    fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION),
+                   ByteEnumField('pdu_type', 0, PDU_TYPE),
+                   ShortField('session_id', 0),
                    IntField('length', STATIC_SERIAL_NOTIFY_LENGTH),
                    IntField('serial_number', 0)]
 
@@ -91,7 +94,9 @@ class RTRSerialQuery(Packet):
 
     '''
     name = 'Serial Query'
-    fields_desc = [ShortField('session_id', 0),
+    fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION),
+                   ByteEnumField('pdu_type', 1, PDU_TYPE),
+                   ShortField('session_id', 0),
                    IntField('length', STATIC_SERIAL_QUERY_LENGTH),
                    IntField('serial_number', 0)]
 
@@ -105,7 +110,9 @@ class RTRResetQuery(Packet):
 
     '''
     name = 'Reset Query'
-    fields_desc = [ShortField('reserved', 0),
+    fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION),
+                   ByteEnumField('pdu_type', 2, PDU_TYPE),
+                   ShortField('reserved', 0),
                    IntField('length', STATIC_RESET_QUERY_LENGTH)]
 
 
@@ -118,11 +125,13 @@ class RTRCacheResponse(Packet):
 
     '''
     name = 'Cache Response'
-    fields_desc = [ShortField('session_id', 0),
+    fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION),
+                   ByteEnumField('pdu_type', 3, PDU_TYPE),
+                   ShortField('session_id', 0),
                    IntField('length', STATIC_CACHE_RESPONSE_LENGTH)]
 
     def guess_payload_class(self, payload):
-        return RTRHeader
+        return RTR
 
 
 class RTRIPv4Prefix(Packet):
@@ -134,7 +143,9 @@ class RTRIPv4Prefix(Packet):
 
     '''
     name = 'IPv4 Prefix'
-    fields_desc = [ShortField('reserved', 0),
+    fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION),
+                   ByteEnumField('pdu_type', 4, PDU_TYPE),
+                   ShortField('reserved', 0),
                    IntField('length', STATIC_IPV4_PREFIX_LENGTH),
                    ByteField('flags', 0),
                    ByteField('shortest_length', 0),
@@ -144,7 +155,7 @@ class RTRIPv4Prefix(Packet):
                    IntField('asn', 0)]
 
     def guess_payload_class(self, payload):
-        return RTRHeader
+        return RTR
 
 
 class RTRIPv6Prefix(Packet):
@@ -156,7 +167,9 @@ class RTRIPv6Prefix(Packet):
 
     '''
     name = 'IPv6 Prefix'
-    fields_desc = [ShortField('reserved', 0),
+    fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION),
+                   ByteEnumField('pdu_type', 6, PDU_TYPE),
+                   ShortField('reserved', 0),
                    IntField('length', STATIC_IPV6_PREFIX_LENGTH),
                    ByteField('flags', 0),
                    ByteField('shortest_length', 0),
@@ -166,7 +179,7 @@ class RTRIPv6Prefix(Packet):
                    IntField('asn', 0)]
 
     def guess_payload_class(self, payload):
-        return RTRHeader
+        return RTR
 
 
 class RTREndofDatav0(Packet):
@@ -178,7 +191,9 @@ class RTREndofDatav0(Packet):
 
     '''
     name = 'End of Data - version 0'
-    fields_desc = [ShortField('session_id', 0),
+    fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION),
+                   ByteEnumField('pdu_type', 7, PDU_TYPE),
+                   ShortField('session_id', 0),
                    IntField('length', STATIC_END_OF_DATA_V0_LENGTH),
                    IntField('serial_number', 0)]
 
@@ -192,7 +207,9 @@ class RTREndofDatav1(Packet):
 
     '''
     name = 'End of Data - version 1'
-    fields_desc = [ShortField('session_id', 0),
+    fields_desc = [ByteEnumField('rtr_version', 1, RTR_VERSION),
+                   ByteEnumField('pdu_type', 7, PDU_TYPE),
+                   ShortField('session_id', 0),
                    IntField('length', STATIC_END_OF_DATA_V1_LENGTH),
                    IntField('serial_number', 0),
                    IntField('refresh_interval', 0),
@@ -209,7 +226,9 @@ class RTRCacheReset(Packet):
 
     '''
     name = 'Reset Query'
-    fields_desc = [ShortField('reserved', 0),
+    fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION),
+                   ByteEnumField('pdu_type', 8, PDU_TYPE),
+                   ShortField('reserved', 0),
                    IntField('length', STATIC_CACHE_RESET_LENGTH)]
 
 
@@ -222,7 +241,9 @@ class RTRRouterKey(Packet):
 
     '''
     name = 'Router Key'
-    fields_desc = [ByteField('flags', 0),
+    fields_desc = [ByteEnumField('rtr_version', 1, RTR_VERSION),
+                   ByteEnumField('pdu_type', 9, PDU_TYPE),
+                   ByteField('flags', 0),
                    ByteField('zeros', 0),
                    IntField('length', None),
                    StrFixedLenField('subject_key_identifier', '', 20),
@@ -246,7 +267,9 @@ class RTRErrorReport(Packet):
 
     '''
     name = 'Error Report'
-    fields_desc = [ShortEnumField('error_code', 0, ERROR_LIST),
+    fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION),
+                   ByteEnumField('pdu_type', 10, PDU_TYPE),
+                   ShortEnumField('error_code', 0, ERROR_LIST),
                    IntField('length', None),
                    FieldLenField('length_of_encaps_PDU',
                                  None, fmt='!I', length_of='erroneous_PDU'),
@@ -264,49 +287,58 @@ def post_build(self, pkt, pay):
         return pkt + pay
 
 
-class RTRHeader(Packet):
+PDU_CLASS_VERSION_0 = {0: RTRSerialNotify,
+                       1: RTRSerialQuery,
+                       2: RTRResetQuery,
+                       3: RTRCacheResponse,
+                       4: RTRIPv4Prefix,
+                       6: RTRIPv6Prefix,
+                       7: RTREndofDatav0,
+                       8: RTRCacheReset,
+                       10: RTRErrorReport}
 
-    '''
+PDU_CLASS_VERSION_1 = {0: RTRSerialNotify,
+                       1: RTRSerialQuery,
+                       2: RTRResetQuery,
+                       3: RTRCacheResponse,
+                       4: RTRIPv4Prefix,
+                       6: RTRIPv6Prefix,
+                       7: RTREndofDatav1,
+                       8: RTRCacheReset,
+                       9: RTRRouterKey,
+                       10: RTRErrorReport}
+
+
+class RTR(Packet):
 
-    RPKI to Router Header from every RTR packet
-    eg. https://tools.ietf.org/html/rfc6810#section-5.2
     '''
-    name = 'RTR Header'
-    fields_desc = [ByteEnumField('rtr_version', 0, RTR_VERSION),
-                   ByteEnumField('pdu_type', 0, PDU_TYPE)]
+    Dummy RPKI to Router generic packet for pre-sorting the packet type
+    eg. https://tools.ietf.org/html/rfc6810#section-5.2
 
-    def guess_payload_class(self, payload):
-        if self.pdu_type == 0:
-            return RTRSerialNotify
-        elif self.pdu_type == 1:
-            return RTRSerialQuery
-        elif self.pdu_type == 2:
-            return RTRResetQuery
-        elif self.pdu_type == 3:
-            return RTRCacheResponse
-        elif self.pdu_type == 4:
-            return RTRIPv4Prefix
-        elif self.pdu_type == 6:
-            return RTRIPv6Prefix
-        elif self.pdu_type == 7:
-            if self.rtr_version == 0:
-                return RTREndofDatav0
-            elif self.rtr_version == 1:
-                return RTREndofDatav1
-        elif self.pdu_type == 8:
-            return RTRCacheReset
-        elif self.pdu_type == 9:
-            if self.rtr_version == 1:
-                return RTRRouterKey
-        elif self.pdu_type == 10:
-            return RTRErrorReport
-
-
-bind_layers(TCP, RTRHeader, dport=323)  # real reserved port
-bind_layers(TCP, RTRHeader, sport=323)  # real reserved port
-bind_layers(TCP, RTRHeader, dport=8282)  # RIPE implementation default port
-bind_layers(TCP, RTRHeader, sport=8282)  # RIPE implementation default port
-bind_layers(RTRHeader, RTRCacheResponse, pdu_type=3, dport=8282)
+    '''
+    name = 'RTR dissector'
+
+    @classmethod
+    def dispatch_hook(cls, _pkt=None, *args, **kargs):
+        '''
+          Attribution of correct type depending on version and pdu_type
+        '''
+        if _pkt and len(_pkt) >= 2:
+            version = orb(_pkt[0])
+            pdu_type = orb(_pkt[1])
+            if version == 0:
+                return PDU_CLASS_VERSION_0[pdu_type]
+            elif version == 1:
+                return PDU_CLASS_VERSION_1[pdu_type]
+        return Raw
+
+
+bind_layers(TCP, RTR, dport=323)  # real reserved port
+bind_layers(TCP, RTR, sport=323)  # real reserved port
+bind_layers(TCP, RTR, dport=8282)  # RIPE implementation default port
+bind_layers(TCP, RTR, sport=8282)  # RIPE implementation default port
+bind_layers(TCP, RTR, dport=2222)  # gortr implementation default port
+bind_layers(TCP, RTR, sport=2222)  # gortr implementation default port
 
 if __name__ == '__main__':
     from scapy.main import interact


Test patch: