From 0427a20e68cb4f7ad466206d97b4ccb33f5a58da Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 10 Feb 2009 14:04:01 +0100 Subject: [PATCH 01/92] Added default random size parameter to RandString and RandBin classes (at last!) --- scapy/volatile.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scapy/volatile.py b/scapy/volatile.py index 17a66e35f..8c8b4533c 100644 --- a/scapy/volatile.py +++ b/scapy/volatile.py @@ -144,9 +144,11 @@ class RandChoice(RandField): return random.choice(self._choice) class RandString(RandField): - def __init__(self, size, chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"): - self.chars = chars + def __init__(self, size=None, chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"): + if size is None: + size = RandNumExpo(0.01) self.size = size + self.chars = chars def _fix(self): s = "" for i in range(self.size): @@ -154,7 +156,7 @@ class RandString(RandField): return s class RandBin(RandString): - def __init__(self, size): + def __init__(self, size=None): RandString.__init__(self, size, "".join(map(chr,range(256)))) From 65eba1397abb56643282c68a6616d8c9cc426735 Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 4 Mar 2009 18:36:44 +0100 Subject: [PATCH 02/92] Fix missing import (Thanks to rmkml) --- scapy/sendrecv.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scapy/sendrecv.py b/scapy/sendrecv.py index 9cf0a88c5..eba6925fa 100644 --- a/scapy/sendrecv.py +++ b/scapy/sendrecv.py @@ -12,6 +12,7 @@ from packet import Gen from utils import warning,get_temp_file import plist from error import log_runtime,log_interactive +from base_classes import SetGen ################# ## Debug class ## From 0c992a389151bdc245cfe0d029d27b6c0e7b9b48 Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 8 Mar 2009 10:56:21 +0100 Subject: [PATCH 03/92] Improve hexdump capture import --- scapy/utils.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/scapy/utils.py b/scapy/utils.py index 2016f4bef..960dfb71f 100644 --- a/scapy/utils.py +++ b/scapy/utils.py @@ -666,7 +666,7 @@ class PcapWriter(RawPcapWriter): RawPcapWriter._write_packet(self, s, sec, usec, caplen, caplen) -re_extract_hexcap = re.compile("^(0x[0-9a-fA-F]{2,}[ :\t]|(0x)?[0-9a-fA-F]{2,}:|(0x)?[0-9a-fA-F]{3,}[: \t]|) *(([0-9a-fA-F]{2} {,2}){,16})") +re_extract_hexcap = re.compile("^((0x)?[0-9a-fA-F]{2,}[ :\t]{,3}|) *(([0-9a-fA-F]{2} {,2}){,16})") def import_hexcap(): p = "" @@ -674,7 +674,7 @@ def import_hexcap(): while 1: l = raw_input().strip() try: - p += re_extract_hexcap.match(l).groups()[3] + p += re_extract_hexcap.match(l).groups()[2] except: warning("Parsing error during hexcap") continue @@ -682,10 +682,7 @@ def import_hexcap(): pass p = p.replace(" ","") - p2="" - for i in range(len(p)/2): - p2 += chr(int(p[2*i:2*i+2],16)) - return p2 + return p.decode("hex") From 75bc91398f00f5a628e5e7242a1f24e487ffaf13 Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 8 Mar 2009 10:56:21 +0100 Subject: [PATCH 04/92] Fixed getmacbyip() to work with Net() objects --- scapy/layers/l2.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py index f69784408..e6aece944 100644 --- a/scapy/layers/l2.py +++ b/scapy/layers/l2.py @@ -4,6 +4,7 @@ ## This program is published under a GPLv2 license import os,struct,time +from scapy.base_classes import Net from scapy.config import conf from scapy.packet import * from scapy.ansmachine import * @@ -43,6 +44,8 @@ conf.netcache.new_cache("arp_cache", 120) # cache entries expire after 120s @conf.commands.register def getmacbyip(ip, chainCC=0): """Return MAC address corresponding to a given IP address""" + if isinstance(ip,Net): + ip = iter(ip).next() tmp = map(ord, inet_aton(ip)) if (tmp[0] & 0xf0) == 0xe0: # mcast @ return "01:00:5e:%.2x:%.2x:%.2x" % (tmp[1]&0x7f,tmp[2],tmp[3]) From c58e3184e2940106d5bd880bfbe8a2efeeadd4c1 Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 8 Mar 2009 10:56:22 +0100 Subject: [PATCH 05/92] Ability to reference a packet's fields in another packet's fields description class Test(Packet): fields_desc = [ IntField("test1",0), SNAP, IntField("test2",1) ] >>> ls(SNAP) OUI : X3BytesField = (0) code : XShortEnumField = (0) >>> ls(Test) test1 : IntField = (0) OUI : X3BytesField = (0) code : XShortEnumField = (0) test2 : IntField = (1) --- scapy/base_classes.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/scapy/base_classes.py b/scapy/base_classes.py index 1a77642e9..221ada6b1 100644 --- a/scapy/base_classes.py +++ b/scapy/base_classes.py @@ -122,8 +122,35 @@ class OID(Gen): class Packet_metaclass(type): def __new__(cls, name, bases, dct): + if "fields_desc" in dct: # perform resolution of references to other packets + current_fld = dct["fields_desc"] + resolved_fld = [] + for f in current_fld: + if isinstance(f, Packet_metaclass): # reference to another fields_desc + for f2 in f.fields_desc: + resolved_fld.append(f2) + else: + resolved_fld.append(f) + else: # look for a field_desc in parent classes + resolved_fld = None + for b in bases: + if hasattr(b,"fields_desc"): + resolved_fld = b.fields_desc + break + + if resolved_fld: # perform default value replacements + final_fld = [] + for f in resolved_fld: + if f.name in dct: + f = f.copy() + f.default = dct[f.name] + del(dct[f.name]) + final_fld.append(f) + + dct["fields_desc"] = final_fld + newcls = super(Packet_metaclass, cls).__new__(cls, name, bases, dct) - for f in newcls.fields_desc: + for f in newcls.fields_desc: f.register_owner(newcls) config.conf.layers.register(newcls) return newcls From 5ea6c8cdf2791322d9ca8803de76d07a08e364fc Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 8 Mar 2009 10:56:22 +0100 Subject: [PATCH 06/92] WARNING: API Change. Removed NewDefaultValues (not needed anymore) A deprecation warning will be issued before complete removal. --- scapy/base_classes.py | 38 ++++++++++++++++---------------------- scapy/layers/dhcp6.py | 13 ------------- scapy/layers/inet6.py | 17 ----------------- scapy/layers/llmnr.py | 1 - test/regression.uts | 3 +-- 5 files changed, 17 insertions(+), 55 deletions(-) diff --git a/scapy/base_classes.py b/scapy/base_classes.py index 221ada6b1..42670e0b9 100644 --- a/scapy/base_classes.py +++ b/scapy/base_classes.py @@ -161,31 +161,25 @@ class Packet_metaclass(type): raise AttributeError(attr) class NewDefaultValues(Packet_metaclass): - """NewDefaultValues metaclass. Example usage: - class MyPacket(Packet): - fields_desc = [ StrField("my_field", "my default value"), ] - - class MyPacket_variant(MyPacket): + """NewDefaultValues is deprecated (not needed anymore) + + remove this: __metaclass__ = NewDefaultValues - my_field = "my new default value" + and it should still work. """ def __new__(cls, name, bases, dct): - fields = None - for b in bases: - if hasattr(b,"fields_desc"): - fields = b.fields_desc - break - if fields is None: - raise error.Scapy_Exception("No fields_desc in superclasses") - - new_fields = [] - for f in fields: - if f.name in dct: - f = f.copy() - f.default = dct[f.name] - del(dct[f.name]) - new_fields.append(f) - dct["fields_desc"] = new_fields + from error import log_loading + import traceback + try: + for tb in traceback.extract_stack()+[("??",-1,None,"")]: + f,l,_,line = tb + if line.startswith("class"): + break + except: + f,l="??",-1 + raise + log_loading.warning("Deprecated use of NewDefaultValues (%s l. %i)." % (f,l)) + return super(NewDefaultValues, cls).__new__(cls, name, bases, dct) class BasePacket(Gen): diff --git a/scapy/layers/dhcp6.py b/scapy/layers/dhcp6.py index db61c38f9..07eee8afb 100644 --- a/scapy/layers/dhcp6.py +++ b/scapy/layers/dhcp6.py @@ -282,7 +282,6 @@ class DHCP6OptClientId(_DHCP6OptGuessPayload): # RFC sect 22.2 class DHCP6OptServerId(DHCP6OptClientId): # RFC sect 22.3 name = "DHCP6 Server Identifier Option" - __metaclass__ = NewDefaultValues optcode = 2 # Should be encapsulated in the option field of IA_NA or IA_TA options @@ -941,7 +940,6 @@ class DHCP6(_DHCP6OptGuessPayload): class DHCP6_Solicit(DHCP6): name = "DHCPv6 Solicit Message" - __metaclass__ = NewDefaultValues msgtype = 1 overload_fields = { UDP: {"sport": 546, "dport": 547} } @@ -955,7 +953,6 @@ class DHCP6_Solicit(DHCP6): class DHCP6_Advertise(DHCP6): name = "DHCPv6 Advertise Message" - __metaclass__ = NewDefaultValues msgtype = 2 overload_fields = { UDP: {"sport": 547, "dport": 546} } @@ -983,7 +980,6 @@ class DHCP6_Advertise(DHCP6): class DHCP6_Request(DHCP6): name = "DHCPv6 Request Message" - __metaclass__ = NewDefaultValues msgtype = 3 ##################################################################### @@ -996,7 +992,6 @@ class DHCP6_Request(DHCP6): class DHCP6_Confirm(DHCP6): name = "DHCPv6 Confirm Message" - __metaclass__ = NewDefaultValues msgtype = 4 ##################################################################### @@ -1018,7 +1013,6 @@ class DHCP6_Confirm(DHCP6): class DHCP6_Renew(DHCP6): name = "DHCPv6 Renew Message" - __metaclass__ = NewDefaultValues msgtype = 5 ##################################################################### @@ -1029,7 +1023,6 @@ class DHCP6_Renew(DHCP6): class DHCP6_Rebind(DHCP6): name = "DHCPv6 Rebind Message" - __metaclass__ = NewDefaultValues msgtype = 6 ##################################################################### @@ -1057,7 +1050,6 @@ class DHCP6_Rebind(DHCP6): class DHCP6_Reply(DHCP6): name = "DHCPv6 Reply Message" - __metaclass__ = NewDefaultValues msgtype = 7 def answers(self, other): @@ -1072,7 +1064,6 @@ class DHCP6_Reply(DHCP6): class DHCP6_Release(DHCP6): name = "DHCPv6 Release Message" - __metaclass__ = NewDefaultValues msgtype = 8 ##################################################################### @@ -1087,7 +1078,6 @@ class DHCP6_Release(DHCP6): class DHCP6_Decline(DHCP6): name = "DHCPv6 Decline Message" - __metaclass__ = NewDefaultValues msgtype = 9 ##################################################################### @@ -1105,7 +1095,6 @@ class DHCP6_Decline(DHCP6): class DHCP6_Reconf(DHCP6): name = "DHCPv6 Reconfigure Message" - __metaclass__ = NewDefaultValues msgtype = 10 overload_fields = { UDP: { "sport": 547, "dport": 546 } } @@ -1124,7 +1113,6 @@ class DHCP6_Reconf(DHCP6): class DHCP6_InfoRequest(DHCP6): name = "DHCPv6 Information Request Message" - __metaclass__ = NewDefaultValues msgtype = 11 def hashret(self): @@ -1172,7 +1160,6 @@ class DHCP6_RelayForward(_DHCP6GuessPayload,Packet): class DHCP6_RelayReply(DHCP6_RelayForward): name = "DHCPv6 Relay Reply Message (Relay Agent/Server Message)" - __metaclass__= NewDefaultValues msgtype = 13 def hashret(self): # We filter on peer address field. return inet_pton(socket.AF_INET6, self.peeraddr) diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py index 87e4f36a7..5466e9857 100644 --- a/scapy/layers/inet6.py +++ b/scapy/layers/inet6.py @@ -1227,7 +1227,6 @@ class ICMPv6EchoRequest(_ICMPv6): class ICMPv6EchoReply(ICMPv6EchoRequest): name = "ICMPv6 Echo Reply" - __metaclass__ = NewDefaultValues type = 129 def answers(self, other): # We could match data content between request and reply. @@ -1262,7 +1261,6 @@ class _ICMPv6ML(_ICMPv6): # Option in a Destination Option Header. class ICMPv6MLQuery(_ICMPv6ML): # RFC 2710 name = "MLD - Multicast Listener Query" - __metaclass__ = NewDefaultValues type = 130 mrd = 10000 mladdr = "::" # 10s for mrd @@ -1278,7 +1276,6 @@ class ICMPv6MLQuery(_ICMPv6ML): # RFC 2710 # Option in a Destination Option Header. class ICMPv6MLReport(_ICMPv6ML): # RFC 2710 name = "MLD - Multicast Listener Report" - __metaclass__ = NewDefaultValues type = 131 overload_fields = {IPv6: {"hlim": 1}} # implementer le hashret et le answers @@ -1291,7 +1288,6 @@ class ICMPv6MLReport(_ICMPv6ML): # RFC 2710 # Option in a Destination Option Header. class ICMPv6MLDone(_ICMPv6ML): # RFC 2710 name = "MLD - Multicast Listener Done" - __metaclass__ = NewDefaultValues type = 132 overload_fields = {IPv6: { "dst": "ff02::2", "hlim": 1}} @@ -1430,7 +1426,6 @@ class ICMPv6NDOptSrcLLAddr(_ICMPv6NDGuessPayload, Packet): class ICMPv6NDOptDstLLAddr(ICMPv6NDOptSrcLLAddr): name = "ICMPv6 Neighbor Discovery Option - Destination Link-Layer Address" - __metaclass__ = NewDefaultValues type = 2 class ICMPv6NDOptPrefixInfo(_ICMPv6NDGuessPayload, Packet): @@ -1695,7 +1690,6 @@ class ICMPv6ND_NS(_ICMPv6NDGuessPayload, _ICMPv6, Packet): class ICMPv6ND_NA(ICMPv6ND_NS): name = "ICMPv6 Neighbor Discovery - Neighbor Advertisement" - __metaclass__ = NewDefaultValues type = 136 R = 1 O = 1 @@ -1729,7 +1723,6 @@ class ICMPv6NDOptSrcAddrList(_ICMPv6NDGuessPayload, Packet): class ICMPv6NDOptTgtAddrList(ICMPv6NDOptSrcAddrList): name = "ICMPv6 Inverse Neighbor Discovery Option - Target Address List" - __metaclass__ = NewDefaultValues type = 10 @@ -2009,19 +2002,16 @@ class ICMPv6NIQueryNOOP(_ICMPv6NIHashret, _ICMPv6): class ICMPv6NIQueryName(ICMPv6NIQueryNOOP): name = "ICMPv6 Node Information Query - IPv6 Name Query" - __metaclass__ = NewDefaultValues qtype = 2 # We ask for the IPv6 address of the peer class ICMPv6NIQueryIPv6(ICMPv6NIQueryNOOP): name = "ICMPv6 Node Information Query - IPv6 Address Query" - __metaclass__ = NewDefaultValues qtype = 3 flags = 0x3E class ICMPv6NIQueryIPv4(ICMPv6NIQueryNOOP): name = "ICMPv6 Node Information Query - IPv4 Address Query" - __metaclass__ = NewDefaultValues qtype = 4 _nireply_code = { 0: "Successful Reply", @@ -2176,27 +2166,22 @@ class ICMPv6NIReplyNOOP(_ICMPv6NIAnswers, _ICMPv6NIHashret, _ICMPv6): class ICMPv6NIReplyName(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - Node Names" - __metaclass__ = NewDefaultValues qtype = 2 class ICMPv6NIReplyIPv6(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - IPv6 addresses" - __metaclass__ = NewDefaultValues qtype = 3 class ICMPv6NIReplyIPv4(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - IPv4 addresses" - __metaclass__ = NewDefaultValues qtype = 4 class ICMPv6NIReplyRefuse(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - Responder refuses to supply answer" - __metaclass__ = NewDefaultValues code = 1 class ICMPv6NIReplyUnknown(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - Qtype unknown to the responder" - __metaclass__ = NewDefaultValues code = 2 @@ -2674,7 +2659,6 @@ class MIP6MH_HoTI(_MobilityHeader): class MIP6MH_CoTI(MIP6MH_HoTI): name = "IPv6 Mobility Header - Care-of Test Init" - __metaclass__ = NewDefaultValues mhtype = 2 def hashret(self): return self.cookie @@ -2703,7 +2687,6 @@ class MIP6MH_HoT(_MobilityHeader): class MIP6MH_CoT(MIP6MH_HoT): name = "IPv6 Mobility Header - Care-of Test" - __metaclass__ = NewDefaultValues mhtype = 4 def hashret(self): return self.cookie diff --git a/scapy/layers/llmnr.py b/scapy/layers/llmnr.py index 62be5226e..561729c87 100644 --- a/scapy/layers/llmnr.py +++ b/scapy/layers/llmnr.py @@ -36,7 +36,6 @@ class LLMNRQuery(Packet): class LLMNRResponse(LLMNRQuery): name = "Link Local Multicast Node Resolution - Response" - __metaclass__ = NewDefaultValues qr = 1 fields_desc = [] diff --git a/test/regression.uts b/test/regression.uts index 49149c991..333d8a2e3 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -188,11 +188,10 @@ assert( _ == "FOO\x01\x02\x03\x04" ) ############ ############ -+ Tests on NewDefaultValues metaclass ++ Tests on default value changes mechanism = Creation of an IPv3 class from IP class with different default values class IPv3(IP): - __metaclass__ = NewDefaultValues version = 3 ttl = 32 From 631c1627fd2abc0b07cc2af0bf391caebd58a9f8 Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 8 Mar 2009 10:56:22 +0100 Subject: [PATCH 07/92] Fix NoPayload.__new__() object creation --- scapy/packet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scapy/packet.py b/scapy/packet.py index 8c31aa12b..b96f3d17a 100644 --- a/scapy/packet.py +++ b/scapy/packet.py @@ -959,12 +959,12 @@ A side effect is that, to obtain "{" and "}" characters, you must use c += "/"+pc return c -class NoPayload(Packet,object): +class NoPayload(Packet): def __new__(cls, *args, **kargs): singl = cls.__dict__.get("__singl__") if singl is None: - cls.__singl__ = singl = object.__new__(cls) - Packet.__init__(singl, *args, **kargs) + cls.__singl__ = singl = Packet.__new__(cls) + Packet.__init__(singl) return singl def __init__(self, *args, **kargs): pass From 454a3b02aeb8004c69aeff436c227a5fada6fba9 Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 8 Mar 2009 10:56:23 +0100 Subject: [PATCH 08/92] Added ability to hook packet class creation Typical use is to register many different kinds of a packet at their declaration. --- scapy/base_classes.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scapy/base_classes.py b/scapy/base_classes.py index 42670e0b9..2f40f61cc 100644 --- a/scapy/base_classes.py +++ b/scapy/base_classes.py @@ -150,16 +150,27 @@ class Packet_metaclass(type): dct["fields_desc"] = final_fld newcls = super(Packet_metaclass, cls).__new__(cls, name, bases, dct) + if hasattr(newcls,"register_variant"): + newcls.register_variant() for f in newcls.fields_desc: f.register_owner(newcls) config.conf.layers.register(newcls) return newcls + def __getattr__(self, attr): for k in self.fields_desc: if k.name == attr: return k raise AttributeError(attr) + def __call__(cls, *args, **kargs): + if "dispatch_hook" in cls.__dict__: + cls = cls.dispatch_hook(*args, **kargs) + i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) + i.__init__(*args, **kargs) + return i + + class NewDefaultValues(Packet_metaclass): """NewDefaultValues is deprecated (not needed anymore) From 2d0ef27ede56949e49969561fff67c22d62ffe0a Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 8 Mar 2009 10:56:23 +0100 Subject: [PATCH 09/92] Catch errors when dissecting a sub-packet inside a PacketList --- scapy/fields.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/scapy/fields.py b/scapy/fields.py index 34cea5286..2253cd892 100644 --- a/scapy/fields.py +++ b/scapy/fields.py @@ -1,4 +1,4 @@ -## This file is part of Scapy +# This file is part of Scapy ## See http://www.secdev.org/projects/scapy for more informations ## Copyright (C) Philippe Biondi ## This program is published under a GPLv2 license @@ -370,7 +370,12 @@ class PacketLenField(PacketField): self.length_from = length_from def getfield(self, pkt, s): l = self.length_from(pkt) - i = self.m2i(pkt, s[:l]) + try: + i = self.m2i(pkt, s[:l]) + except Exception: + if conf.debug_dissector: + raise + i = conf.raw_layer(load=s[:l]) return s[l:],i @@ -415,13 +420,20 @@ class PacketListField(PacketField): if c <= 0: break c -= 1 - p = self.m2i(pkt,remain) - if 'Padding' in p: - pad = p['Padding'] - remain = pad.load - del(pad.underlayer.payload) - else: + try: + p = self.m2i(pkt,remain) + except Exception: + if conf.debug_dissector: + raise + p = conf.raw_layer(load=remain) remain = "" + else: + if 'Padding' in p: + pad = p['Padding'] + remain = pad.load + del(pad.underlayer.payload) + else: + remain = "" lst.append(p) return remain+ret,lst def addfield(self, pkt, s, val): From 55392dc323a5f116f7512cec09aa60682e92d882 Mon Sep 17 00:00:00 2001 From: Phil Date: Mon, 9 Mar 2009 18:23:06 +0100 Subject: [PATCH 10/92] Added IP options support * * * --- scapy/base_classes.py | 2 +- scapy/layers/inet.py | 167 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 156 insertions(+), 13 deletions(-) diff --git a/scapy/base_classes.py b/scapy/base_classes.py index 2f40f61cc..6d36053c0 100644 --- a/scapy/base_classes.py +++ b/scapy/base_classes.py @@ -189,7 +189,7 @@ class NewDefaultValues(Packet_metaclass): except: f,l="??",-1 raise - log_loading.warning("Deprecated use of NewDefaultValues (%s l. %i)." % (f,l)) + log_loading.warning("Deprecated (no more needed) use of NewDefaultValues (%s l. %i)." % (f,l)) return super(NewDefaultValues, cls).__new__(cls, name, bases, dct) diff --git a/scapy/layers/inet.py b/scapy/layers/inet.py index 702d50d9f..769c39669 100644 --- a/scapy/layers/inet.py +++ b/scapy/layers/inet.py @@ -34,18 +34,160 @@ class IPTools: return self.ottl()-self.ttl-1 +_ip_options_names = { 0: "end_of_list", + 1: "nop", + 2: "security", + 3: "loose_source_route", + 4: "timestamp", + 5: "extended_security", + 6: "commercial_security", + 7: "record_route", + 8: "stream_id", + 9: "strict_source_route", + 10: "experimental_measurement", + 11: "mtu_probe", + 12: "mtu_reply", + 13: "flow_control", + 14: "access_control", + 15: "encode", + 16: "imi_traffic_descriptor", + 17: "extended_IP", + 18: "traceroute", + 19: "address_extension", + 20: "router_alert", + 21: "selective_directed_broadcast_mode", + 23: "dynamic_packet_state", + 24: "upstream_multicast_packet", + 25: "quick_start", + 30: "rfc4727_experiment", + } + -class IPoptionsField(StrField): - def i2m(self, pkt, x): - return x+"\x00"*(3-((len(x)+3)%4)) - def getfield(self, pkt, s): - opsz = (pkt.ihl-5)*4 - if opsz < 0: - warning("bad ihl (%i). Assuming ihl=5"%pkt.ihl) - opsz = 0 - return s[opsz:],s[:opsz] - def randval(self): - return RandBin(RandNum(0,39)) +class _IPOption_HDR(Packet): + fields_desc = [ BitField("copy_flag",0, 1), + BitEnumField("optclass",0,2,{0:"control",2:"debug"}), + BitEnumField("option",0,5, _ip_options_names) ] + +class IPOption(Packet): + fields_desc = [ _IPOption_HDR, + FieldLenField("length", None, fmt="B", # Only option 0 and 1 have no length and value + length_of="value", adjust=lambda pkt,l:l+2), + StrLenField("value", "",length_from=lambda pkt:pkt.length-2) ] + + def extract_padding(self, p): + return "",p + + registered_ip_options = {} + @classmethod + def register_variant(cls): + cls.registered_ip_options[cls.option.default] = cls + @classmethod + def dispatch_hook(cls, pkt=None, *args, **kargs): + if pkt: + opt = ord(pkt[0])&0x1f + if opt in cls.registered_ip_options: + return cls.registered_ip_options[opt] + return cls + +class IPOption_EOL(IPOption): + option = 0 + fields_desc = [ _IPOption_HDR ] + + +class IPOption_NOP(IPOption): + option=1 + fields_desc = [ _IPOption_HDR ] + +class IPOption_Security(IPOption): + copy_flag = 1 + option = 2 + fields_desc = [ _IPOption_HDR, + ByteField("length", 11), + ShortField("security",0), + ShortField("compartment",0), + ShortField("handling_restrictions",0), + StrFixedLenField("transmission_control_code","xxx",3), + ] + +class IPOption_LSRR(IPOption): + name = "IP Option Loose Source and Record Route" + copy_flag = 1 + option = 3 + fields_desc = [ _IPOption_HDR, + FieldLenField("length", None, fmt="B", + length_of="routers", adjust=lambda pkt,l:l+3), + ByteField("pointer",4), # 4 is first IP + FieldListField("routers",[],IPField("","0.0.0.0"), + length_from=lambda pkt:pkt.length-3) + ] + def get_current_router(self): + return self.routers[self.pointer/4-1] + +class IPOption_RR(IPOption_LSRR): + name = "IP Option Record Route" + option = 7 + +class IPOption_SSRR(IPOption_LSRR): + name = "IP Option Strict Source and Record Route" + option = 9 + +class IPOption_Stream_Id(IPOption): + name = "IP Option Stream ID" + option = 8 + fields_desc = [ _IPOption_HDR, + ByteField("length", 4), + ShortField("security",0), ] + +class IPOption_MTU_Probe(IPOption): + name = "IP Option MTU Probe" + option = 11 + fields_desc = [ _IPOption_HDR, + ByteField("length", 4), + ShortField("mtu",0), ] + +class IPOption_MTU_Reply(IPOption_MTU_Probe): + name = "IP Option MTU Reply" + option = 12 + +class IPOption_Traceroute(IPOption): + copy_flag = 1 + option = 18 + fields_desc = [ _IPOption_HDR, + ByteField("length", 12), + ShortField("id",0), + ShortField("outbound_hops",0), + ShortField("return_hops",0), + IPField("originator_ip","0.0.0.0") ] + +class IPOption_Address_Extension(IPOption): + name = "IP Option Address Extension" + copy_flag = 1 + option = 19 + fields_desc = [ _IPOption_HDR, + ByteField("length", 10), + IPField("src_ext","0.0.0.0"), + IPField("dst_ext","0.0.0.0") ] + +class IPOption_Router_Alert(IPOption): + name = "IP Option Router Alert" + copy_flag = 1 + option = 20 + fields_desc = [ _IPOption_HDR, + ByteField("length", 4), + ShortEnumField("alert",0, {0:"router_shall_examine_packet"}), ] + + +class IPOption_SDBM(IPOption): + name = "IP Option Selective Directed Broadcast Mode" + copy_flag = 1 + option = 21 + fields_desc = [ _IPOption_HDR, + FieldLenField("length", None, fmt="B", + length_of="addresses", adjust=lambda pkt,l:l+2), + FieldListField("addresses",[],IPField("","0.0.0.0"), + length_from=lambda pkt:pkt.length-2) + ] + TCPOptions = ( @@ -181,9 +323,10 @@ class IP(Packet, IPTools): #IPField("src", "127.0.0.1"), Emph(SourceIPField("src","dst")), Emph(IPField("dst", "127.0.0.1")), - IPoptionsField("options", "") ] + PacketListField("options", [], IPOption, length_from=lambda p:p.ihl*4-20) ] def post_build(self, p, pay): ihl = self.ihl + p += "\0"*((-len(p))%4) # pad IP options if needed if ihl is None: ihl = len(p)/4 p = chr(((self.version&0xf)<<4) | ihl&0x0f)+p[1:] From 9e27071acc946f67a4a9b473edbbdd027fcbcb9d Mon Sep 17 00:00:00 2001 From: Phil Date: Mon, 9 Mar 2009 18:23:09 +0100 Subject: [PATCH 11/92] Added regression tets for IP Options --- test/regression.uts | 48 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/test/regression.uts b/test/regression.uts index 333d8a2e3..b626e1a9c 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -896,7 +896,53 @@ a=ATMT6() a.run() assert( _ == 'Mercury' ) - + ++ Test IP options + += IP options individual assembly +~ IP options +str(IPOption()) +assert(_ == '\x00\x02') +str(IPOption_NOP()) +assert(_ == '\x01') +str(IPOption_EOL()) +assert(_ == '\x00') +str(IPOption_LSRR(routers=["1.2.3.4","5.6.7.8"])) +assert(_ == '\x83\x0b\x04\x01\x02\x03\x04\x05\x06\x07\x08') + += IP options individual dissection +~ IP options +IPOption("\x00") +assert(_.option == 0 and isinstance(_, IPOption_EOL)) +IPOption("\x01") +assert(_.option == 1 and isinstance(_, IPOption_NOP)) +lsrr='\x83\x0b\x04\x01\x02\x03\x04\x05\x06\x07\x08' +p=IPOption_LSRR(lsrr) +p +q=IPOption(lsrr) +q +assert(p == q) + += IP assembly and dissection with options +~ IP options +IP(src="9.10.11.12",dst="13.14.15.16",options=IPOption_SDBM(addresses=["1.2.3.4","5.6.7.8"]))/TCP() +str(_) +assert(_ == 'H\x00\x004\x00\x01\x00\x00@\x06\xa2q\t\n\x0b\x0c\r\x0e\x0f\x10\x95\n\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00_K\x00\x00') +q=IP(_) +q +assert( isinstance(q.options[0],IPOption_SDBM) ) +assert( q[IPOption_SDBM].addresses[1] == "5.6.7.8" ) +IP(src="9.10.11.12", dst="13.14.15.16", options=[IPOption_NOP(),IPOption_LSRR(routers=["1.2.3.4","5.6.7.8"]),IPOption_Security(transmission_control_code="XYZ")])/TCP() +str(_) +assert(_ == 'K\x00\x00@\x00\x01\x00\x00@\x06\xf3\x83\t\n\x0b\x0c\r\x0e\x0f\x10\x01\x83\x0b\x04\x01\x02\x03\x04\x05\x06\x07\x08\x82\x0b\x00\x00\x00\x00\x00\x00XYZ\x00\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00_K\x00\x00') +IP(_) +q=_ +assert(q[IPOption_LSRR].get_current_router() == "1.2.3.4") +assert(q[IPOption_Security].transmission_control_code == "XYZ") +assert(q[TCP].flags == 2) + + + # Scapy6 Regression Test Campaign From 740b08ffb3fd119c28e7b784dbcc0272485fcac8 Mon Sep 17 00:00:00 2001 From: Phil Date: Mon, 9 Mar 2009 18:23:10 +0100 Subject: [PATCH 12/92] Fixed some bugs in PPP layers --- scapy/layers/ppp.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/scapy/layers/ppp.py b/scapy/layers/ppp.py index 06062a9d6..2135500dd 100644 --- a/scapy/layers/ppp.py +++ b/scapy/layers/ppp.py @@ -263,13 +263,14 @@ class PPP_IPCP_Specific_Option_metaclass(PPP_IPCP_Option_metaclass): def __new__(cls, name, bases, dct): newcls = super(PPP_IPCP_Specific_Option_metaclass, cls).__new__(cls, name, bases, dct) PPP_IPCP_Option._register(newcls) + return newcls class PPP_IPCP_Option_IPAddress(PPP_IPCP_Option): __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: IP Address" fields_desc = [ ByteEnumField("type" , 3 , _PPP_ipcpopttypes), - FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), + FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), IPField("data","0.0.0.0"), ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] @@ -277,7 +278,7 @@ class PPP_IPCP_Option_DNS1(PPP_IPCP_Option): __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: DNS1 Address" fields_desc = [ ByteEnumField("type" , 129 , _PPP_ipcpopttypes), - FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), + FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), IPField("data","0.0.0.0"), ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] @@ -285,7 +286,7 @@ class PPP_IPCP_Option_DNS2(PPP_IPCP_Option): __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: DNS2 Address" fields_desc = [ ByteEnumField("type" , 131 , _PPP_ipcpopttypes), - FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), + FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), IPField("data","0.0.0.0"), ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] @@ -293,7 +294,7 @@ class PPP_IPCP_Option_NBNS1(PPP_IPCP_Option): __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: NBNS1 Address" fields_desc = [ ByteEnumField("type" , 130 , _PPP_ipcpopttypes), - FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), + FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), IPField("data","0.0.0.0"), ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] @@ -301,7 +302,7 @@ class PPP_IPCP_Option_NBNS2(PPP_IPCP_Option): __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: NBNS2 Address" fields_desc = [ ByteEnumField("type" , 132 , _PPP_ipcpopttypes), - FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), + FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), IPField("data","0.0.0.0"), ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] @@ -338,12 +339,13 @@ class PPP_ECP_Specific_Option_metaclass(PPP_ECP_Option_metaclass): def __new__(cls, name, bases, dct): newcls = super(PPP_ECP_Specific_Option_metaclass, cls).__new__(cls, name, bases, dct) PPP_ECP_Option._register(newcls) + return newcls class PPP_ECP_Option_OUI(PPP_ECP_Option): __metaclass__=PPP_ECP_Specific_Option_metaclass fields_desc = [ ByteEnumField("type" , 0 , _PPP_ecpopttypes), - FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), + FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), StrFixedLenField("oui","",3), ByteField("subtype",0), StrLenField("data", "", length_from=lambda p:p.len-6) ] @@ -356,12 +358,14 @@ class PPP_ECP(Packet): FieldLenField("len" , None, fmt="H", length_of="options", adjust=lambda p,x:x+4 ), PacketListField("options", [], PPP_ECP_Option, length_from=lambda p:p.len-4,) ] -bind_layers( Ether, PPPoED, type=34915) -bind_layers( Ether, PPPoE, type=34916) -bind_layers( CookedLinux, PPPoED, proto=34915) -bind_layers( CookedLinux, PPPoE, proto=34916) +bind_layers( Ether, PPPoED, type=0x8863) +bind_layers( Ether, PPPoE, type=0x8864) +bind_layers( CookedLinux, PPPoED, proto=0x8863) +bind_layers( CookedLinux, PPPoE, proto=0x8864) bind_layers( PPPoE, PPP, code=0) bind_layers( HDLC, PPP, ) bind_layers( PPP, IP, proto=33) bind_layers( PPP, PPP_IPCP, proto=0x8021) bind_layers( PPP, PPP_ECP, proto=0x8053) +bind_layers( Ether, PPP_IPCP, type=0x8021) +bind_layers( Ether, PPP_ECP, type=0x8053) From f8bd0f6c76b5499802ce8c58d5bfc2bcc8a7f823 Mon Sep 17 00:00:00 2001 From: Phil Date: Mon, 9 Mar 2009 18:23:10 +0100 Subject: [PATCH 13/92] Added regression tests for PPP --- test/regression.uts | 63 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/regression.uts b/test/regression.uts index b626e1a9c..f53dc4404 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -942,6 +942,69 @@ assert(q[IPOption_Security].transmission_control_code == "XYZ") assert(q[TCP].flags == 2) ++ Test PPP + += PPP/HDLC +~ ppp hdlc +HDLC()/PPP()/PPP_IPCP() +str(_) +s=_ +assert(s == '\xff\x03\x80!\x01\x00\x00\x04') +PPP(s) +p=_ +assert(HDLC in p) +assert(p[HDLC].control==3) +assert(p[PPP].proto==0x8021) +PPP(s[2:]) +q=_ +assert(HDLC not in q) +assert(q[PPP].proto==0x8021) + + += PPP IPCP +~ ppp ipcp +PPP('\x80!\x01\x01\x00\x10\x03\x06\xc0\xa8\x01\x01\x02\x06\x00-\x0f\x01') +p=_ +assert(p[PPP_IPCP].code == 1) +assert(p[PPP_IPCP_Option_IPAddress].data=="192.168.1.1") +assert(p[PPP_IPCP_Option].data == '\x00-\x0f\x01') +p=PPP()/PPP_IPCP(options=[PPP_IPCP_Option_DNS1(data="1.2.3.4"),PPP_IPCP_Option_DNS2(data="5.6.7.8"),PPP_IPCP_Option_NBNS2(data="9.10.11.12")]) +str(p) +assert(_ == '\x80!\x01\x00\x00\x16\x81\x06\x01\x02\x03\x04\x83\x06\x05\x06\x07\x08\x84\x06\t\n\x0b\x0c') +PPP(_) +q=_ +assert(str(p) == str(q)) +assert(PPP(str(q))==q) +PPP()/PPP_IPCP(options=[PPP_IPCP_Option_DNS1(data="1.2.3.4"),PPP_IPCP_Option_DNS2(data="5.6.7.8"),PPP_IPCP_Option(type=123,data="ABCDEFG"),PPP_IPCP_Option_NBNS2(data="9.10.11.12")]) +p=_ +str(p) +assert(_ == '\x80!\x01\x00\x00\x1f\x81\x06\x01\x02\x03\x04\x83\x06\x05\x06\x07\x08{\tABCDEFG\x84\x06\t\n\x0b\x0c') +PPP(_) +q=_ +assert( q[PPP_IPCP_Option].type == 123 ) +assert( q[PPP_IPCP_Option].data == 'ABCDEFG' ) +assert( q[PPP_IPCP_Option_NBNS2].data == '9.10.11.12' ) + + += PPP ECP +~ ppp ecp + +PPP()/PPP_ECP(options=[PPP_ECP_Option_OUI(oui="XYZ")]) +p=_ +str(p) +assert(_ == '\x80S\x01\x00\x00\n\x00\x06XYZ\x00') +PPP(_) +q=_ +assert( str(p)==str(q) ) +PPP()/PPP_ECP(options=[PPP_ECP_Option_OUI(oui="XYZ"),PPP_ECP_Option(type=1,data="ABCDEFG")]) +p=_ +str(p) +assert(_ == '\x80S\x01\x00\x00\x13\x00\x06XYZ\x00\x01\tABCDEFG') +PPP(_) +q=_ +assert( str(p) == str(q) ) +assert( q[PPP_ECP_Option].data == "ABCDEFG" ) + # Scapy6 Regression Test Campaign From e19645c613dad2af8088773688ca4673d1a2ac66 Mon Sep 17 00:00:00 2001 From: Phil Date: Mon, 9 Mar 2009 18:23:10 +0100 Subject: [PATCH 14/92] Migrated PPP metaclass use to new Packet metaclass * * * --- scapy/layers/ppp.py | 82 ++++++++++++++------------------------------- 1 file changed, 26 insertions(+), 56 deletions(-) diff --git a/scapy/layers/ppp.py b/scapy/layers/ppp.py index 2135500dd..67fc3f2fb 100644 --- a/scapy/layers/ppp.py +++ b/scapy/layers/ppp.py @@ -188,20 +188,14 @@ class HDLC(Packet): fields_desc = [ XByteField("address",0xff), XByteField("control",0x03) ] -class PPP_metaclass(Packet_metaclass): - def __call__(self, _pkt=None, *args, **kargs): - cls = self - if _pkt and _pkt[0] == '\xff': - cls = HDLC - i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) - i.__init__(_pkt=_pkt, *args, **kargs) - return i - - class PPP(Packet): - __metaclass__ = PPP_metaclass name = "PPP Link Layer" fields_desc = [ ShortEnumField("proto", 0x0021, _PPP_proto) ] + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and _pkt[0] == '\xff': + cls = HDLC + return cls _PPP_conftypes = { 1:"Configure-Request", 2:"Configure-Ack", @@ -218,19 +212,6 @@ _PPP_conftypes = { 1:"Configure-Request", 15:"Reset-Ack", } -class PPP_Option_metaclass(Packet_metaclass): - _known_options={} - def __call__(self, _pkt=None, *args, **kargs): - cls = self - if _pkt: - t = ord(_pkt[0]) - cls = self._known_options.get(t,self) - i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) - i.__init__(_pkt=_pkt, *args, **kargs) - return i - def _register(self, cls): - self._known_options[cls.fields_desc[0].default] = cls - ### PPP IPCP stuff (RFC 1332) @@ -245,13 +226,7 @@ _PPP_ipcpopttypes = { 1:"IP-Addresses (Deprecated)", 132:"Secondary-NBNS-Address"} - -class PPP_IPCP_Option_metaclass(PPP_Option_metaclass): - _known_options={} - - class PPP_IPCP_Option(Packet): - __metaclass__=PPP_IPCP_Option_metaclass name = "PPP IPCP Option" fields_desc = [ ByteEnumField("type" , None , _PPP_ipcpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), @@ -259,15 +234,19 @@ class PPP_IPCP_Option(Packet): def extract_padding(self, pay): return "",pay -class PPP_IPCP_Specific_Option_metaclass(PPP_IPCP_Option_metaclass): - def __new__(cls, name, bases, dct): - newcls = super(PPP_IPCP_Specific_Option_metaclass, cls).__new__(cls, name, bases, dct) - PPP_IPCP_Option._register(newcls) - return newcls - + registered_options = {} + @classmethod + def register_variant(cls): + cls.registered_options[cls.type.default] = cls + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + o = ord(_pkt[0]) + return cls.registered_options.get(o, cls) + return cls + class PPP_IPCP_Option_IPAddress(PPP_IPCP_Option): - __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: IP Address" fields_desc = [ ByteEnumField("type" , 3 , _PPP_ipcpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), @@ -275,7 +254,6 @@ class PPP_IPCP_Option_IPAddress(PPP_IPCP_Option): ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] class PPP_IPCP_Option_DNS1(PPP_IPCP_Option): - __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: DNS1 Address" fields_desc = [ ByteEnumField("type" , 129 , _PPP_ipcpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), @@ -283,7 +261,6 @@ class PPP_IPCP_Option_DNS1(PPP_IPCP_Option): ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] class PPP_IPCP_Option_DNS2(PPP_IPCP_Option): - __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: DNS2 Address" fields_desc = [ ByteEnumField("type" , 131 , _PPP_ipcpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), @@ -291,7 +268,6 @@ class PPP_IPCP_Option_DNS2(PPP_IPCP_Option): ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] class PPP_IPCP_Option_NBNS1(PPP_IPCP_Option): - __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: NBNS1 Address" fields_desc = [ ByteEnumField("type" , 130 , _PPP_ipcpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), @@ -299,7 +275,6 @@ class PPP_IPCP_Option_NBNS1(PPP_IPCP_Option): ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] class PPP_IPCP_Option_NBNS2(PPP_IPCP_Option): - __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: NBNS2 Address" fields_desc = [ ByteEnumField("type" , 132 , _PPP_ipcpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), @@ -307,8 +282,6 @@ class PPP_IPCP_Option_NBNS2(PPP_IPCP_Option): ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] - - class PPP_IPCP(Packet): fields_desc = [ ByteEnumField("code" , 1, _PPP_conftypes), XByteField("id", 0 ), @@ -321,13 +294,7 @@ class PPP_IPCP(Packet): _PPP_ecpopttypes = { 0:"OUI", 1:"DESE", } -class PPP_ECP_Option_metaclass(PPP_Option_metaclass): - _known_options={} - - - class PPP_ECP_Option(Packet): - __metaclass__=PPP_ECP_Option_metaclass name = "PPP ECP Option" fields_desc = [ ByteEnumField("type" , None , _PPP_ecpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), @@ -335,15 +302,18 @@ class PPP_ECP_Option(Packet): def extract_padding(self, pay): return "",pay -class PPP_ECP_Specific_Option_metaclass(PPP_ECP_Option_metaclass): - def __new__(cls, name, bases, dct): - newcls = super(PPP_ECP_Specific_Option_metaclass, cls).__new__(cls, name, bases, dct) - PPP_ECP_Option._register(newcls) - return newcls - + registered_options = {} + @classmethod + def register_variant(cls): + cls.registered_options[cls.type.default] = cls + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + o = ord(_pkt[0]) + return cls.registered_options.get(o, cls) + return cls class PPP_ECP_Option_OUI(PPP_ECP_Option): - __metaclass__=PPP_ECP_Specific_Option_metaclass fields_desc = [ ByteEnumField("type" , 0 , _PPP_ecpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), StrFixedLenField("oui","",3), From 3ad5266798a3b6ce1c7bfeafb935ef954332a8b8 Mon Sep 17 00:00:00 2001 From: Phil Date: Mon, 9 Mar 2009 18:23:11 +0100 Subject: [PATCH 15/92] imported patch l2_migration --- scapy/layers/l2.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py index e6aece944..a923b0e7d 100644 --- a/scapy/layers/l2.py +++ b/scapy/layers/l2.py @@ -127,20 +127,8 @@ class ARPSourceMACField(MACField): ### Layers -class Ether_or_Dot3_metaclass(Packet_metaclass): - def __call__(self, _pkt=None, *args, **kargs): - cls = self - if _pkt and len(_pkt) >= 14: - if struct.unpack("!H", _pkt[12:14])[0] <= 1500: - cls = Dot3 - else: - cls = Ether - i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) - i.__init__(_pkt=_pkt, *args, **kargs) - return i class Ether(Packet): - __metaclass__ = Ether_or_Dot3_metaclass name = "Ethernet" fields_desc = [ DestMACField("dst"), SourceMACField("src"), @@ -154,9 +142,15 @@ class Ether(Packet): return 0 def mysummary(self): return self.sprintf("%src% > %dst% (%type%)") + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 14: + if struct.unpack("!H", _pkt[12:14])[0] <= 1500: + return Dot3 + return cls + class Dot3(Packet): - __metaclass__ = Ether_or_Dot3_metaclass name = "802.3" fields_desc = [ DestMACField("dst"), MACField("src", ETHER_ANY), @@ -170,6 +164,12 @@ class Dot3(Packet): return 0 def mysummary(self): return "802.3 %s > %s" % (self.src, self.dst) + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 14: + if struct.unpack("!H", _pkt[12:14])[0] > 1500: + return Ether + return cls class LLC(Packet): From dd093b185aa6ccd4a5a362674dc966a59a386814 Mon Sep 17 00:00:00 2001 From: Phil Date: Mon, 9 Mar 2009 18:23:12 +0100 Subject: [PATCH 16/92] Added MultiEnumField for fields whose text also depends on another field ex: ICMP code field --- scapy/fields.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/scapy/fields.py b/scapy/fields.py index 2253cd892..a8793d1f6 100644 --- a/scapy/fields.py +++ b/scapy/fields.py @@ -769,6 +769,36 @@ class XShortEnumField(ShortEnumField): return self.i2s[x] return lhex(x) +class MultiEnumField(EnumField): + def __init__(self, name, default, enum, depends_on, fmt = "H"): + + self.depends_on = depends_on + self.i2s_multi = enum + self.s2i_multi = {} + self.s2i_all = {} + for m in enum: + self.s2i_multi[m] = s2i = {} + for k,v in enum[m].iteritems(): + s2i[v] = k + self.s2i_all[v] = k + Field.__init__(self, name, default, fmt) + def any2i_one(self, pkt, x): + if type (x) is str: + v = self.depends_on(pkt) + if v in self.s2i_multi: + s2i = self.s2i_multi[v] + if x in s2i: + return s2i[x] + return self.s2i_all[x] + return x + def i2repr_one(self, pkt, x): + v = self.depends_on(pkt) + if v in self.i2s_multi: + return self.i2s_multi[v].get(x,x) + return x + + + # Little endian long field class LELongField(Field): def __init__(self, name, default): From c53f4314c61ce515c78bea3c1420889c54452d80 Mon Sep 17 00:00:00 2001 From: Phil Date: Mon, 9 Mar 2009 18:23:13 +0100 Subject: [PATCH 17/92] Added description text to ICMP code field --- scapy/layers/inet.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/scapy/layers/inet.py b/scapy/layers/inet.py index 769c39669..15897eb2d 100644 --- a/scapy/layers/inet.py +++ b/scapy/layers/inet.py @@ -517,10 +517,37 @@ icmptypes = { 0 : "echo-reply", 17 : "address-mask-request", 18 : "address-mask-reply" } +icmpcodes = { 3 : { 0 : "network-unreachable", + 1 : "host-unreachable", + 2 : "protocol-unreachable", + 3 : "port-unreachable", + 4 : "fragmentation-needed", + 5 : "source-route-failed", + 6 : "network-unknown", + 7 : "host-unknown", + 9 : "network-prohibited", + 10 : "host-prohibited", + 11 : "TOS-network-unreachable", + 12 : "TOS-host-unreachable", + 13 : "communication-prohibited", + 14 : "host-precedence-violation", + 15 : "precedence-cutoff", }, + 5 : { 0 : "network-redirect", + 1 : "host-redirect", + 2 : "TOS-network-redirect", + 3 : "TOS-host-redirect", }, + 11 : { 0 : "ttl-zero-during-transit", + 1 : "ttl-zero-during-reassembly", }, + 12 : { 0 : "ip-header-bad", + 1 : "required-option-missing", }, } + + + + class ICMP(Packet): name = "ICMP" fields_desc = [ ByteEnumField("type",8, icmptypes), - ByteField("code",0), + MultiEnumField("code",0, icmpcodes, depends_on=lambda pkt:pkt.type,fmt="B"), XShortField("chksum", None), ConditionalField(XShortField("id",0), lambda pkt:pkt.type in [0,8,13,14,15,16,17,18]), ConditionalField(XShortField("seq",0), lambda pkt:pkt.type in [0,8,13,14,15,16,17,18]), From 2598314b8623afb0ca69f4c38f5ae9eafaee92f8 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 10 Mar 2009 17:45:17 +0100 Subject: [PATCH 18/92] Added RandSingNum() to provide singular numbers pool --- scapy/volatile.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/scapy/volatile.py b/scapy/volatile.py index 8c8b4533c..10373a9d5 100644 --- a/scapy/volatile.py +++ b/scapy/volatile.py @@ -3,7 +3,7 @@ ## Copyright (C) Philippe Biondi ## This program is published under a GPLv2 license -import random,time +import random,time,math from base_classes import Net from utils import corrupt_bits,corrupt_bytes @@ -380,9 +380,33 @@ class RandRegExp(RandField): return RandRegExp.stack_fix(stack[1:], index) def __repr__(self): return "<%s [%r]>" % (self.__class__.__name__, self._regexp) + +class RandSingularity(RandChoice): + pass - +class RandSingNum(RandSingularity): + @staticmethod + def make_power_of_two(end): + sign = 1 + if end == 0: + end = 1 + if end < 0: + end = -end + sign = -1 + end_n = int(math.log(end)/math.log(2))+1 + return set([sign*2**i for i in range(end_n)]) + def __init__(self, mn, mx): + sing = set([0, mn, mx, int((mn+mx)/2)]) + sing |= self.make_power_of_two(mn) + sing |= self.make_power_of_two(mx) + for i in sing.copy(): + sing.add(i+1) + sing.add(i-1) + for i in sing.copy(): + if not mn <= i <= mx: + sing.remove(i) + self._choice = list(sing) # Automatic timestamp From 57679e227e41645e935ac7823ccc8077ad1600f7 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 10 Mar 2009 17:45:18 +0100 Subject: [PATCH 19/92] Added RandSingByte, Short, etc. derived from RandSingNum --- scapy/volatile.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/scapy/volatile.py b/scapy/volatile.py index 10373a9d5..be811419a 100644 --- a/scapy/volatile.py +++ b/scapy/volatile.py @@ -409,6 +409,40 @@ class RandSingNum(RandSingularity): self._choice = list(sing) +class RandSingByte(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, 0, 2L**8-1) + +class RandSingSByte(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, -2L**7, 2L**7-1) + +class RandSingShort(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, 0, 2L**16-1) + +class RandSingSShort(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, -2L**15, 2L**15-1) + +class RandSingInt(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, 0, 2L**32-1) + +class RandSingSInt(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, -2L**31, 2L**31-1) + +class RandSingLong(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, 0, 2L**64-1) + +class RandSingSLong(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, -2L**63, 2L**63-1) + + + # Automatic timestamp class AutoTime(VolatileValue): From 2311b12cba83a8bdebd7bc4307345318afc6ec59 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 10 Mar 2009 17:45:19 +0100 Subject: [PATCH 20/92] Added RandSByte and RandSShort --- scapy/volatile.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scapy/volatile.py b/scapy/volatile.py index be811419a..db389a308 100644 --- a/scapy/volatile.py +++ b/scapy/volatile.py @@ -115,10 +115,18 @@ class RandByte(RandSeq): def __init__(self): RandSeq.__init__(self, 0, 2L**8-1) +class RandSByte(RandSeq): + def __init__(self): + RandSeq.__init__(self, -2L**7, 2L**7-1) + class RandShort(RandSeq): def __init__(self): RandSeq.__init__(self, 0, 2L**16-1) +class RandSShort(RandSeq): + def __init__(self): + RandSeq.__init__(self, -2L**15, 2L**15-1) + class RandInt(RandSeq): def __init__(self): RandSeq.__init__(self, 0, 2L**32-1) From 93d313d3f0ea979ac02c9ebd576fe7bd23f1a4de Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 10 Mar 2009 18:12:46 +0100 Subject: [PATCH 21/92] Added RandSingString --- scapy/volatile.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/scapy/volatile.py b/scapy/volatile.py index db389a308..9f7c03f2b 100644 --- a/scapy/volatile.py +++ b/scapy/volatile.py @@ -449,8 +449,69 @@ class RandSingSLong(RandSingNum): def __init__(self): RandSingNum.__init__(self, -2L**63, 2L**63-1) +class RandSingString(RandSingularity): + def __init__(self): + self._choice = [ "", + "%x", + "%%", + "%s", + "%i", + "%n", + "%x%x%x%x%x%x%x%x%x", + "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + "%", + "%%%", + "A"*4096, + "\x00"*4096, + "\xff"*4096, + "\x7f"*4096, + "\x80"*4096, + " "*4096, + "\\"*4096, + "("*4096, + "../"*1024, + "/"*1024, + "${HOME}"*512, + " or 1=1 --", + "' or 1=1 --", + '" or 1=1 --', + " or 1=1; #", + "' or 1=1; #", + '" or 1=1; #', + ";reboot;", + "$(reboot)", + "`reboot`", + "index.php%00", + "\x00", + "%00", + "\\", + "../../../../../../../../../../../../../../../../../etc/passwd", + "%2e%2e%2f" * 20 + "etc/passwd", + "%252e%252e%252f" * 20 + "boot.ini", + "..%c0%af" * 20 + "etc/passwd", + "..%c0%af" * 20 + "boot.ini", + "//etc/passwd", + r"..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\boot.ini", + "AUX:", + "CLOCK$", + "COM:", + "CON:", + "LPT:", + "LST:", + "NUL:", + "CON:", + r"C:\CON\CON", + r"C:\boot.ini", + r"\\myserver\share", + "foo.exe:", + "foo.exe\\", ] + + + + + # Automatic timestamp class AutoTime(VolatileValue): From e7fc11f225307cf910ec8dc8e6a0b070bab60be2 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 10 Mar 2009 18:12:52 +0100 Subject: [PATCH 22/92] Added RandPool --- scapy/volatile.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scapy/volatile.py b/scapy/volatile.py index 9f7c03f2b..c49bb2c31 100644 --- a/scapy/volatile.py +++ b/scapy/volatile.py @@ -507,10 +507,19 @@ class RandSingString(RandSingularity): "foo.exe\\", ] - - - - +class RandPool(RandField): + def __init__(self, *args): + """Each parameter is a volatile object or a couple (volatile object, weight)""" + pool = [] + for p in args: + w = 1 + if type(p) is tuple: + p,w = p + pool += [p]*w + self._pool = pool + def _fix(self): + r = random.choice(self._pool) + return r._fix() # Automatic timestamp From 84a76140b829b9cfacaba4aee4cf5e21ad8d2a8d Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 17 Mar 2009 08:40:02 -0700 Subject: [PATCH 23/92] Made send(string) work again --- scapy/sendrecv.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scapy/sendrecv.py b/scapy/sendrecv.py index eba6925fa..09c91881b 100644 --- a/scapy/sendrecv.py +++ b/scapy/sendrecv.py @@ -204,6 +204,8 @@ def sndrcv(pks, pkt, timeout = 2, inter = 0, verbose=None, chainCC=0, retry=0, m def __gen_send(s, x, inter=0, loop=0, count=None, verbose=None, *args, **kargs): + if type(x) is str: + x = Raw(load=x) if not isinstance(x, Gen): x = SetGen(x) if verbose is None: From f30f46c4868b1459272cefbf9d6742aa3eae05c1 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 17 Mar 2009 08:50:38 -0700 Subject: [PATCH 24/92] Align ARPingResult.show() output if MAC address OUI is displayed --- scapy/layers/l2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py index a923b0e7d..e1ae21a8d 100644 --- a/scapy/layers/l2.py +++ b/scapy/layers/l2.py @@ -432,7 +432,7 @@ class ARPingResult(SndRcvList): def show(self): for s,r in self.res: - print r.sprintf("%Ether.src% %ARP.psrc%") + print r.sprintf("%19s,Ether.src% %ARP.psrc%") From ef3d1a2e20ac3c0bc1ea5f090f9b264f6efce0a3 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 17 Mar 2009 08:55:50 -0700 Subject: [PATCH 25/92] Improved ARP.my_summary() --- scapy/layers/l2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py index e1ae21a8d..97ed5a1a3 100644 --- a/scapy/layers/l2.py +++ b/scapy/layers/l2.py @@ -340,11 +340,11 @@ class ARP(Packet): return "",s def mysummary(self): if self.op == self.is_at: - return "ARP is at %s says %s" % (self.hwsrc, self.psrc) + return self.sprintf("ARP is at %hwsrc% says %psrc%") elif self.op == self.who_has: - return "ARP who has %s says %s" % (self.pdst, self.psrc) + return self.sprintf("ARP who has %pdst% says %psrc%") else: - return "ARP %ARP.op% %ARP.psrc% > %ARP.pdst%" + return self.sprintf("ARP %op% %psrc% > %pdst%") conf.neighbor.register_l3(Ether, ARP, lambda l2,l3: getmacbyip(l3.pdst)) From eabb142ea3deb184f84d0add2d301df7c9ae41a9 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 14 Apr 2009 01:09:06 +0200 Subject: [PATCH 26/92] Fixed use of ARPHDR_ instead of DLT_ consts for pcap writing --- scapy/layers/dot11.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scapy/layers/dot11.py b/scapy/layers/dot11.py index cf73c1669..5c3aec63e 100644 --- a/scapy/layers/dot11.py +++ b/scapy/layers/dot11.py @@ -362,11 +362,12 @@ bind_layers( Dot11Auth, Dot11Elt, ) bind_layers( Dot11Elt, Dot11Elt, ) -conf.l2types.register(801, Dot11) -conf.l2types.register_num2layer(105, Dot11) -conf.l2types.register(802, PrismHeader) -conf.l2types.register(803, RadioTap) -conf.l2types.register_num2layer(127, RadioTap) +conf.l2types.register(105, Dot11) +conf.l2types.register_num2layer(801, Dot11) +conf.l2types.register(119, PrismHeader) +conf.l2types.register_num2layer(802, PrismHeader) +conf.l2types.register(127, RadioTap) +conf.l2types.register_num2layer(803, RadioTap) class WiFi_am(AnsweringMachine): From 71ad2b6422bb7119319b971d348cd4aca434ce91 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 14 Apr 2009 01:16:00 +0200 Subject: [PATCH 27/92] Made in-place and zip execution work with python2.6 (ticket #195) --- run_scapy | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/run_scapy b/run_scapy index 5f51187b3..c4723fe52 100755 --- a/run_scapy +++ b/run_scapy @@ -1,3 +1,3 @@ #! /bin/sh DIR=$(dirname $0) -PYTHONPATH=$DIR exec python -m scapy/ +PYTHONPATH=$DIR exec python -m scapy.__init__ diff --git a/setup.py b/setup.py index 45282c3e6..4f8a9b8cb 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ import os EZIP_HEADER="""#! /bin/sh -PYTHONPATH=$0/%s exec python -m scapy +PYTHONPATH=$0/%s exec python -m scapy.__init__ """ def make_ezipfile(base_name, base_dir, verbose=0, dry_run=0): From d1b464300c45c271c16e3f9474a6d69624a7c6b0 Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 15 Apr 2009 00:33:19 +0200 Subject: [PATCH 28/92] WARNING: API changes. Rationalized random volatile objects naming. RandDraw*: random draw from a set without replacement. Guarantees that a name is not drawn twice before all elements have been drawn. Rand*: random draw from a set with replacement. RandSing*: singular values from a set, drawn at random with replacement. --- scapy/volatile.py | 69 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/scapy/volatile.py b/scapy/volatile.py index c49bb2c31..7f4d78fd4 100644 --- a/scapy/volatile.py +++ b/scapy/volatile.py @@ -75,13 +75,13 @@ class RandField(VolatileValue): pass class RandNum(RandField): + """Instances evaluate to random integers in selected range""" min = 0 max = 0 def __init__(self, min, max): self.min = min self.max = max def _fix(self): - # XXX: replace with sth that guarantee unicity return random.randrange(self.min, self.max+1) class RandNumGamma(RandField): @@ -105,43 +105,76 @@ class RandNumExpo(RandField): def _fix(self): return self.base+int(round(random.expovariate(self.lambd))) -class RandSeq(RandNum): +class RandDraw(RandNum): + """Instances evaluate to integer sampling without replacement from the given interval""" def __init__(self, min, max): self.seq = RandomSequence(min,max) def _fix(self): return self.seq.next() -class RandByte(RandSeq): +class RandByte(RandNum): def __init__(self): - RandSeq.__init__(self, 0, 2L**8-1) + RandNum.__init__(self, 0, 2L**8-1) -class RandSByte(RandSeq): +class RandSByte(RandNum): def __init__(self): - RandSeq.__init__(self, -2L**7, 2L**7-1) + RandNum.__init__(self, -2L**7, 2L**7-1) -class RandShort(RandSeq): +class RandShort(RandNum): def __init__(self): - RandSeq.__init__(self, 0, 2L**16-1) + RandNum.__init__(self, 0, 2L**16-1) -class RandSShort(RandSeq): +class RandSShort(RandNum): def __init__(self): - RandSeq.__init__(self, -2L**15, 2L**15-1) + RandNum.__init__(self, -2L**15, 2L**15-1) -class RandInt(RandSeq): +class RandInt(RandNum): def __init__(self): - RandSeq.__init__(self, 0, 2L**32-1) + RandNum.__init__(self, 0, 2L**32-1) -class RandSInt(RandSeq): +class RandSInt(RandNum): def __init__(self): - RandSeq.__init__(self, -2L**31, 2L**31-1) + RandNum.__init__(self, -2L**31, 2L**31-1) -class RandLong(RandSeq): +class RandLong(RandNum): def __init__(self): - RandSeq.__init__(self, 0, 2L**64-1) + RandNum.__init__(self, 0, 2L**64-1) -class RandSLong(RandSeq): +class RandSLong(RandNum): def __init__(self): - RandSeq.__init__(self, -2L**63, 2L**63-1) + RandNum.__init__(self, -2L**63, 2L**63-1) + +class RandDrawByte(RandDraw): + def __init__(self): + RandDraw.__init__(self, 0, 2L**8-1) + +class RandDrawSByte(RandDraw): + def __init__(self): + RandDraw.__init__(self, -2L**7, 2L**7-1) + +class RandDrawShort(RandDraw): + def __init__(self): + RandDraw.__init__(self, 0, 2L**16-1) + +class RandDrawSShort(RandDraw): + def __init__(self): + RandDraw.__init__(self, -2L**15, 2L**15-1) + +class RandDrawInt(RandDraw): + def __init__(self): + RandDraw.__init__(self, 0, 2L**32-1) + +class RandDrawSInt(RandDraw): + def __init__(self): + RandDraw.__init__(self, -2L**31, 2L**31-1) + +class RandDrawLong(RandDraw): + def __init__(self): + RandDraw.__init__(self, 0, 2L**64-1) + +class RandDrawSLong(RandDraw): + def __init__(self): + RandDraw.__init__(self, -2L**63, 2L**63-1) class RandChoice(RandField): def __init__(self, *args): From cc165246d9951b50c031b210d3626b086e30f040 Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Fri, 20 Feb 2009 21:43:30 +0100 Subject: [PATCH 29/92] Converted an import statement in an example to Scapy v2 --- doc/scapy/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/scapy/usage.rst b/doc/scapy/usage.rst index 50c749e0b..fa177e397 100644 --- a/doc/scapy/usage.rst +++ b/doc/scapy/usage.rst @@ -931,7 +931,7 @@ This program uses the ``sniff()`` callback (paramter prn). The store parameter i :: #! /usr/bin/env python - from scapy import * + from scapy.all import * def arp_monitor_callback(pkt): if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at From 652baabe1fffc3dff6c3f11d37763e529ee3cb8f Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Fri, 20 Feb 2009 22:42:29 +0100 Subject: [PATCH 30/92] Added several tutorial sections, one-liners and recipes by Peter Kacherginsky Peter had written a very nice tutorial on his web site http://thesprawl.org/memdump/doku.php?id=tools:scapy and kindly allowed me to add it to our docs under the CC BY-NC-SA-2.5 license. --- doc/scapy/usage.rst | 479 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 477 insertions(+), 2 deletions(-) diff --git a/doc/scapy/usage.rst b/doc/scapy/usage.rst index fa177e397..8c1de9eea 100644 --- a/doc/scapy/usage.rst +++ b/doc/scapy/usage.rst @@ -336,6 +336,103 @@ If there is a limited rate of answers, you can specify a time interval to wait b Received 100 packets, got 3 answers, remaining 9 packets (, ) + +SYN Scans +--------- + +.. index:: + single: SYN Scan + +Classic SYN Scan can be initialized by executing the following command from Scapy's prompt:: + + >>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S")) + +The above will send a single SYN packet to Google's port 80 and will quit after receving a single response:: + + Begin emission: + .Finished to send 1 packets. + * + Received 2 packets, got 1 answers, remaining 0 packets + >> + +From the above output, we can see Google returned “SA” or SYN-ACK flags indicating an open port. + +Use either notations to scan ports 400 through 443 on the system: + + >>> sr(IP(dst="192.168.1.1")/TCP(sport=666,dport=(440,443),flags="S")) + +or + + >>> sr(IP(dst="192.168.1.1")/TCP(sport=RandShort(),dport=[440,441,442,443],flags="S")) + +In order to quickly review responses simply request a summary of collected packets:: + + >>> ans,unans = _ + >>> ans.summary() + IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:440 S ======> IP / TCP 192.168.1.1:440 > 192.168.1.100:ftp-data RA / Padding + IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:441 S ======> IP / TCP 192.168.1.1:441 > 192.168.1.100:ftp-data RA / Padding + IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:442 S ======> IP / TCP 192.168.1.1:442 > 192.168.1.100:ftp-data RA / Padding + IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:https S ======> IP / TCP 192.168.1.1:https > 192.168.1.100:ftp-data SA / Padding + +The above will display stimulus/response pairs for answered probes. We can display only the information we are interested in by using a simple loop: + + >>> ans.summary( lambda(s,r): r.sprintf("%TCP.sport% \t %TCP.flags%") ) + 440 RA + 441 RA + 442 RA + https SA + +Even better, a table can be built using the ``make_table()`` function to display information about multiple targets:: + + >>> ans,unans = sr(IP(dst=["192.168.1.1","yahoo.com","slashdot.org"])/TCP(dport=[22,80,443],flags="S")) + Begin emission: + .......*.**.......Finished to send 9 packets. + **.*.*..*.................. + Received 362 packets, got 8 answers, remaining 1 packets + >>> ans.make_table( + ... lambda(s,r): (s.dst, s.dport, + ... r.sprintf("{TCP:%TCP.flags%}{ICMP:%IP.src% - %ICMP.type%}"))) + 66.35.250.150 192.168.1.1 216.109.112.135 + 22 66.35.250.150 - dest-unreach RA - + 80 SA RA SA + 443 SA SA SA + +The above example will even print the ICMP error type if the ICMP packet was received as a response instead of expected TCP. + +For larger scans, we could be interested in displaying only certain responses. The example below will only display packets with the “SA” flag set:: + + >>> ans.nsummary(lfilter = lambda (s,r): r.sprintf("%TCP.flags%") ====== "SA") + 0003 IP / TCP 192.168.1.100:ftp_data > 192.168.1.1:https S ======> IP / TCP 192.168.1.1:https > 192.168.1.100:ftp_data SA + +In case we want to do some expert analysis of responses, we can use the following command to indicate which ports are open:: + + >>> ans.summary(lfilter = lambda (s,r): r.sprintf("%TCP.flags%") ====== "SA",prn=lambda(s,r):r.sprintf("%TCP.sport% is open")) + https is open + +Again, for larger scans we can build a table of open ports:: + + >>> ans.filter(lambda (s,r):TCP in r and r[TCP].flags&2).make_table(lambda (s,r): + ... (s.dst, s.dport, "X")) + 66.35.250.150 192.168.1.1 216.109.112.135 + 80 X - X + 443 X X X + +If all of the above methods were not enough, Scapy includes a report_ports() function which not only automates the SYN scan, but also produces a LaTeX output with collected results:: + + >>> report_ports("192.168.1.1",(440,443)) + Begin emission: + ...*.**Finished to send 4 packets. + * + Received 8 packets, got 4 answers, remaining 0 packets + '\\begin{tabular}{|r|l|l|}\n\\hline\nhttps & open & SA \\\\\n\\hline\n440 + & closed & TCP RA \\\\\n441 & closed & TCP RA \\\\\n442 & closed & + TCP RA \\\\\n\\hline\n\\hline\n\\end{tabular}\n' + + TCP traceroute -------------- @@ -503,6 +600,24 @@ We can easily capture some packets or even clone tcpdump or tethereal. If no int ---[ Padding ]--- load = '\n_\x00\x0b' +For even more control over displayed information we can use the ``sprintf()`` function:: + + >>> pkts = sniff(prn=lambda x:x.sprintf("{IP:%IP.src% -> %IP.dst%\n}{Raw:%Raw.load%\n}")) + 192.168.1.100 -> 64.233.167.99 + + 64.233.167.99 -> 192.168.1.100 + + 192.168.1.100 -> 64.233.167.99 + + 192.168.1.100 -> 64.233.167.99 + 'GET / HTTP/1.1\r\nHost: 64.233.167.99\r\nUser-Agent: Mozilla/5.0 + (X11; U; Linux i686; en-US; rv:1.8.1.8) Gecko/20071022 Ubuntu/7.10 (gutsy) + Firefox/2.0.0.8\r\nAccept: text/xml,application/xml,application/xhtml+xml, + text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language: + en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: + ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection: + keep-alive\r\nCache-Control: max-age=0\r\n\r\n' + We can sniff and do passive OS fingerprinting:: >>> p @@ -579,6 +694,143 @@ Here is an example of a (h)ping-like functionnality : you always send the same s IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S + +Importing and Exporting Data +---------------------------- +PCAP +^^^^ + +It is often useful to save capture packets to pcap file for use at later time or with different applications:: + + >>> wrpcap("temp.cap",pkts) + +To restore previously saved pcap file: + + >>> pkts = rdpcap("temp.cap") + +or + + >>> pkts = sniff(offline="temp.cap") + +Hexdump +^^^^^^^ + +Scapy allows you to export recorded packets in various hex formats. + +Use ``hexdump()`` to display one or more packets using classic hexdump format:: + + >>> hexdump(pkt) + 0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E. + 0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@.@.Z|...... + 0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI.. + 0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................ + 0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$% + 0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345 + 0060 36 37 67 + +Hexdump above can be reimported back into Scapy using ``import_hexcap()``:: + + >>> pkt_hex = Ether(import_hexcap()) + 0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E. + 0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@.@.Z|...... + 0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI.. + 0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................ + 0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$% + 0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345 + 0060 36 37 67 + >>> pkt_hex + >>> + +Hex string +^^^^^^^^^^ + +You can also convert entire packet into a hex string using the ``str()`` function:: + + >>> pkts = sniff(count = 1) + >>> pkt = pkts[0] + >>> pkt + >>> + >>> pkt_str = str(pkt) + >>> pkt_str + '\x00PV\xfc\xceP\x00\x0c)+S\x19\x08\x00E\x00\x00T\x00\x00@\x00@\x01Z|\xc0\xa8 + \x19\x82\x04\x02\x02\x01\x08\x00\x9c\x90Za\x00\x01\xe6\xdapI\xb6\xe5\x08\x00 + \x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b + \x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567' + +We can reimport the produced hex string by selecting the appropriate starting layer (e.g. ``Ether()``). + + >>> new_pkt = Ether(pkt_str) + >>> new_pkt + >>> + +Base64 +^^^^^^ + +Using the ``export_object()`` function, Scapy can export a base64 encoded Python data structure representing a packet:: + + >>> pkt + >>> + >>> export_object(pkt) + eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST + OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao + bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT + WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6 + ... + +The output above can be reimported back into Skype using ``import_object()``:: + + >>> new_pkt = import_object() + eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST + OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao + bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT + WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6 + ... + >>> new_pkt + >>> + +Sessions +^^^^^^^^ + +At last Scapy is capable of saving all session variables using the ``save_session()`` function: + +>>> dir() +['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_str', 'pkts'] +>>> save_session("session.scapy") + +Next time you start Scapy you can load the previous saved session using the ``load_session()`` command:: + + >>> dir() + ['__builtins__', 'conf'] + >>> load_session("session.scapy") + >>> dir() + ['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_str', 'pkts'] + + Making tables ------------- @@ -805,6 +1057,97 @@ you can have a kind of FakeAP:: Simple one-liners ================= + +ACK Scan +-------- + +Using Scapy's powerful packet crafting facilities we can quick replicate classic TCP Scans. +For example, the following string will be sent to simulate an ACK Scan:: + + >>> ans,unans = sr(IP(dst="www.slashdot.org")/TCP(dport=[80,666],flags="A")) + +We can find unfiltered ports in answered packets:: + + >>> for s,r in ans: + ... if s[TCP].dport == r[TCP].sport: + ... print str(s[TCP].dport) + " is unfiltered" + +Similarly, filtered ports can be found with unanswered packets:: + + >>> for s in unans: + ... print str(s[TCP].dport) + " is filtered" + + +Xmas Scan +--------- + +Xmas Scan can be launced using the following command: + +>>> ans,unans = sr(IP(dst="192.168.1.1")/TCP(dport=666,flags="FPU") ) + +Checking RST responses will reveal closed ports on the target. + +IP Scan +------- + +A lower level IP Scan can be used to enumerate supported protocols:: + + >>> ans,unans=sr(IP(dst="192.168.1.1",proto=(0,255))/"SCAPY",retry=2) + + +ARP Ping +-------- + +The fastest way to discover hosts on a local ethernet network is to use the ARP Ping method:: + + >>> ans,unans=srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.1.0/24"),timeout=2) + +Answers can be reviewed with the following command:: + + >>> ans.summary(lambda (s,r): r.sprintf("%Ether.src% %ARP.psrc%") ) + +Scapy also includes a built-in arping() function which performs similar to the above two commands: + + >>> arping("192.168.1.*") + + +ICMP Ping +--------- + +Classical ICMP Ping can be emulated using the following command:: + + >>> ans,unans=sr(IP(dst="192.168.1.1-254")/ICMP()) + +Information on live hosts can be collected with the following request:: + + >>> ans.summary(lambda (s,r): r.sprintf("%IP.src% is alive") ) + + +TCP Ping +-------- + +In cases where ICMP echo requests are blocked, we can still use various TCP Pings such as TCP SYN Ping below:: + + >>> ans,unans=sr( IP(dst="192.168.1.*")/TCP(dport=80,flags="S") ) + +Any response to our probes will indicate a live host. We can collect results with the following command:: + + >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") ) + + +UDP Ping +-------- + +If all else fails there is always UDP Ping which will produce ICMP Port unreachable errors from live hosts. Here you can pick any port which is most likely to be closed, such as port 0:: + + >>> ans,unans=sr( IP(dst="192.168.*.1-10")/UDP(dport=0) ) + +Once again, results can be collected with this command: + + >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") ) + + + Classical attacks ----------------- @@ -872,9 +1215,32 @@ Visualizing the results in a list:: res.nsummary(prn=lambda (s,r): r.src, lfilter=lambda (s,r): r.haslayer(ISAKMP) ) - + + +Advanced traceroute +------------------- + +TCP SYN traceroute +^^^^^^^^^^^^^^^^^^ + +:: + + >>> ans,unans=sr(IP(dst="4.2.2.1",ttl=(1,10))/TCP(dport=53,flags="S")) + +Results would be:: + + >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src%\t{ICMP:%ICMP.type%}\t{TCP:%TCP.flags%}")) + 192.168.1.1 time-exceeded + 68.86.90.162 time-exceeded + 4.79.43.134 time-exceeded + 4.79.43.133 time-exceeded + 4.68.18.126 time-exceeded + 4.68.123.38 time-exceeded + 4.2.2.1 SA + + UDP traceroute --------------- +^^^^^^^^^^^^^^ Tracerouting an UDP application like we do with TCP is not reliable, because there's no handshake. We need to give an applicative payload (DNS, ISAKMP, @@ -888,6 +1254,26 @@ We can visualize the results as a list of routers:: res.make_table(lambda (s,r): (s.dst, s.ttl, r.src)) +DNS traceroute +^^^^^^^^^^^^^^ + +We can perform a DNS traceroute by specifying a complete packet in ``l4`` parameter of ``traceroute()`` function:: + + >>> ans,unans=traceroute("4.2.2.1",l4=UDP(sport=RandShort())/DNS(qd=DNSQR(qname="thesprawl.org"))) + Begin emission: + ..*....******...******.***...****Finished to send 30 packets. + *****...***............................... + Received 75 packets, got 28 answers, remaining 2 packets + 4.2.2.1:udp53 + 1 192.168.1.1 11 + 4 68.86.90.162 11 + 5 4.79.43.134 11 + 6 4.79.43.133 11 + 7 4.68.18.62 11 + 8 4.68.123.6 11 + 9 4.2.2.1 + ... + Etherleaking ------------ @@ -919,6 +1305,20 @@ make a packet jump to another VLAN:: >>> sendp(Ether()/Dot1Q(vlan=2)/Dot1Q(vlan=7)/IP(dst=target)/ICMP()) +Wireless sniffing +----------------- + +The following command will display information similar to most wireless sniffers:: + +>>> sniff(iface="ath0",prn=lambda x:x.sprintf("{Dot11Beacon:%Dot11.addr3%\t%Dot11Beacon.info%\t%PrismHeader.channel%\tDot11Beacon.cap%}")) + +The above command will produce output similar to the one below:: + + 00:00:00:01:02:03 netgear 6L ESS+privacy+PBCC + 11:22:33:44:55:66 wireless_100 6L short-slot+ESS+privacy + 44:55:66:00:11:22 linksys 6L short-slot+ESS+privacy + 12:34:56:78:90:12 NETGEAR 6L short-slot+ESS+privacy+short-preamble + Recipes ======= @@ -992,6 +1392,7 @@ See also http://en.wikipedia.org/wiki/Rogue_DHCP + Firewalking ----------- @@ -1010,6 +1411,23 @@ only his own NIC’s IP are reachable with this TTL:: >>> for i in unans: print i.dst +TCP Timestamp Filtering +------------------------ + +Problem +^^^^^^^ + +Many firewalls include a rule to drop TCP packets that do not have TCP Timestamp option set which is a common occurrence in popular port scanners. + +Solution +^^^^^^^^ + +To allow Scapy to reach target destination additional options must be used:: + + >>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S",options=[('Timestamp',(0,0))])) + + + Viewing packets with Wireshark ------------------------------ @@ -1042,4 +1460,61 @@ You can tell Scapy where to find the Wireshark executable by changing the ``conf +OS Fingerprinting +----------------- + +ISN +^^^ + +Scapy can be used to analyze ISN (Initial Sequence Number) increments to possibly discover vulnerable systems. First we will collect target responses by sending a number of SYN probes in a loop:: + + >>> ans,unans=srloop(IP(dst="192.168.1.1")/TCP(dport=80,flags="S")) + +Once we obtain a reasonable number of responses we can start analyzing collected data with something like this: + + >>> temp = 0 + >>> for s,r in ans: + ... temp = r[TCP].seq - temp + ... print str(r[TCP].seq) + "\t+" + str(temp) + ... + 4278709328 +4275758673 + 4279655607 +3896934 + 4280642461 +4276745527 + 4281648240 +4902713 + 4282645099 +4277742386 + 4283643696 +5901310 + +nmap_fp +^^^^^^^ + +If you have nmap installed you can use it's active os fingerprinting database with Scapy. First make sure that version 1 of signature database is located in the path specified by:: + + >>> conf.nmap_base + +Scapy includes a built-in ``nmap_fp()`` function which implements same probes as in Nmap's OS Detection engine:: + + >>> nmap_fp("192.168.1.1",oport=443,cport=1) + Begin emission: + .****..**Finished to send 8 packets. + *................................................ + Received 58 packets, got 7 answers, remaining 1 packets + (1.0, ['Linux 2.4.0 - 2.5.20', 'Linux 2.4.19 w/grsecurity patch', + 'Linux 2.4.20 - 2.4.22 w/grsecurity.org patch', 'Linux 2.4.22-ck2 (x86) + w/grsecurity.org and HZ=1000 patches', 'Linux 2.4.7 - 2.6.11']) + +p0f +^^^ + +If you have p0f installed on your system, you can use it to guess OS name and version right from Scapy (only SYN database is used). First make sure that p0f database exists in the path specified by:: + + >>> conf.p0f_base + +For example to guess OS from a single captured packet: + + >>> sniff(prn=prnp0f) + 192.168.1.100:54716 - Linux 2.6 (newer, 1) (up: 24 hrs) + -> 74.125.19.104:www (distance 0) + + + From 75b054975aba4ec04bae3d2cfe750e2d0bd62554 Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Fri, 20 Feb 2009 22:48:39 +0100 Subject: [PATCH 31/92] Layout corrections in one-liners --- doc/scapy/usage.rst | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/scapy/usage.rst b/doc/scapy/usage.rst index 8c1de9eea..a1b788bf1 100644 --- a/doc/scapy/usage.rst +++ b/doc/scapy/usage.rst @@ -1081,9 +1081,9 @@ Similarly, filtered ports can be found with unanswered packets:: Xmas Scan --------- -Xmas Scan can be launced using the following command: +Xmas Scan can be launced using the following command:: ->>> ans,unans = sr(IP(dst="192.168.1.1")/TCP(dport=666,flags="FPU") ) + >>> ans,unans = sr(IP(dst="192.168.1.1")/TCP(dport=666,flags="FPU") ) Checking RST responses will reveal closed ports on the target. @@ -1153,21 +1153,21 @@ Classical attacks Malformed packets:: - send(IP(dst="10.1.1.5", ihl=2, version=3)/ICMP()) + >>> send(IP(dst="10.1.1.5", ihl=2, version=3)/ICMP()) Ping of death (Muuahahah):: - send( fragment(IP(dst="10.0.0.5")/ICMP()/("X"*60000)) ) + >>> send( fragment(IP(dst="10.0.0.5")/ICMP()/("X"*60000)) ) Nestea attack:: - send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*10)) - send(IP(dst=target, id=42, frag=48)/("X"*116)) - send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*224)) + >>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*10)) + >>> send(IP(dst=target, id=42, frag=48)/("X"*116)) + >>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*224)) Land attack (designed for Microsoft Windows):: - send(IP(src=target,dst=target)/TCP(sport=135,dport=135)) + >>> send(IP(src=target,dst=target)/TCP(sport=135,dport=135)) ARP cache poisoning ------------------- @@ -1176,12 +1176,12 @@ its ARP cache through a VLAN hopping attack. Classic ARP cache poisoning:: - send( Ether(dst=clientMAC)/ARP(op="who-has", psrc=gateway, pdst=client), + >>> send( Ether(dst=clientMAC)/ARP(op="who-has", psrc=gateway, pdst=client), inter=RandNum(10,40), loop=1 ) ARP cache poisoning with double 802.1q encapsulation:: - send( Ether(dst=clientMAC)/Dot1Q(vlan=1)/Dot1Q(vlan=2) + >>> send( Ether(dst=clientMAC)/Dot1Q(vlan=1)/Dot1Q(vlan=2) /ARP(op="who-has", psrc=gateway, pdst=client), inter=RandNum(10,40), loop=1 ) @@ -1190,14 +1190,14 @@ TCP Port Scanning Send a TCP SYN on each port. Wait for a SYN-ACK or a RST or an ICMP error:: - res,unans = sr( IP(dst="target") + >>> res,unans = sr( IP(dst="target") /TCP(flags="S", dport=(1,1024)) ) Possible result visualization: open ports :: - res.nsummary( lfilter=lambda (s,r): (r.haslayer(TCP) and (r.getlayer(TCP).flags & 2)) ) + >>> res.nsummary( lfilter=lambda (s,r): (r.haslayer(TCP) and (r.getlayer(TCP).flags & 2)) ) IKE Scanning @@ -1206,14 +1206,14 @@ IKE Scanning We try to identify VPN concentrators by sending ISAKMP Security Association proposals and receiving the answers:: - res,unans = sr( IP(dst="192.168.1.*")/UDP() + >>> res,unans = sr( IP(dst="192.168.1.*")/UDP() /ISAKMP(init_cookie=RandString(8), exch_type="identity prot.") /ISAKMP_payload_SA(prop=ISAKMP_payload_Proposal()) ) Visualizing the results in a list:: - res.nsummary(prn=lambda (s,r): r.src, lfilter=lambda (s,r): r.haslayer(ISAKMP) ) + >>> res.nsummary(prn=lambda (s,r): r.src, lfilter=lambda (s,r): r.haslayer(ISAKMP) ) @@ -1246,12 +1246,12 @@ Tracerouting an UDP application like we do with TCP is not reliable, because there's no handshake. We need to give an applicative payload (DNS, ISAKMP, NTP, etc.) to deserve an answer:: - res,unans = sr(IP(dst="target", ttl=(1,20)) + >>> res,unans = sr(IP(dst="target", ttl=(1,20)) /UDP()/DNS(qd=DNSQR(qname="test.com")) We can visualize the results as a list of routers:: - res.make_table(lambda (s,r): (s.dst, s.ttl, r.src)) + >>> res.make_table(lambda (s,r): (s.dst, s.ttl, r.src)) DNS traceroute From 5c9e5c133e1ea33f61806ce09b541b91c8423b65 Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Fri, 20 Feb 2009 22:57:03 +0100 Subject: [PATCH 32/92] Increased version number. Copyright for 2009 --- doc/scapy/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/scapy/conf.py b/doc/scapy/conf.py index 0165d5188..9097facc6 100644 --- a/doc/scapy/conf.py +++ b/doc/scapy/conf.py @@ -36,15 +36,15 @@ master_doc = 'index' # General substitutions. project = 'Scapy' -copyright = '2008, Philippe Biondi and the Scapy community' +copyright = '2008, 2009 Philippe Biondi and the Scapy community' # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # # The short X.Y version. -version = '2.0.0' +version = '2.0.1' # The full version, including alpha/beta/rc tags. -release = '2.0.0' +release = '2.0.1' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: From 887c550432f5649704bf6173e489fda6eb5cc8a8 Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Fri, 20 Feb 2009 23:17:05 +0100 Subject: [PATCH 33/92] Added credits for Peter Kacherginsky --- doc/scapy/backmatter.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/scapy/backmatter.rst b/doc/scapy/backmatter.rst index c54e06873..9fa0791e8 100644 --- a/doc/scapy/backmatter.rst +++ b/doc/scapy/backmatter.rst @@ -6,6 +6,7 @@ Credits - Philippe Biondi is Scapy's author. He has also written most of the documentation. - Fred Raynal wrote the chapter on building and dissecting packets. - Sebastien Martini added some details in "Handling default values: automatic computation". +- Peter Kacherginsky contributed several tutorial sections, one-liners and recipes. - Dirk Loss integrated and restructured the existing docs to make this book. He has also written the installation instructions for Windows. From 1cd6bc09aad6a9465e53105ce82b25409794115e Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Sun, 15 Mar 2009 21:26:05 +0100 Subject: [PATCH 34/92] Added explanation of variable length fields This is taken from Phil's article on the Wiki: http://trac.secdev.org/scapy/wiki/LengthFields --- doc/scapy/build_dissect.rst | 81 ++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/doc/scapy/build_dissect.rst b/doc/scapy/build_dissect.rst index e014a915c..79287417f 100644 --- a/doc/scapy/build_dissect.rst +++ b/doc/scapy/build_dissect.rst @@ -933,7 +933,86 @@ Lists and lengths PacketLenField # used e.g. in ISAKMP_payload_Proposal PacketListField -The FieldListField and LengthFields articles on the Wiki have more info on this topic. + +Variable length fields +^^^^^^^^^^^^^^^^^^^^^^ + +This is about how fields that have a variable length can be handled with Scapy. These fields usually know their length from another field. Let's call them varfield and lenfield. The idea is to make each field reference the other so that when a packet is dissected, varfield can know its length from lenfield when a packet is assembled, you don't have to fill lenfield, that will deduce its value directly from varfield value. + +Problems arise whe you realize that the relation between lenfield and varfield is not always straightforward. Sometimes, lenfield indicates a length in bytes, sometimes a number of objects. Sometimes the length includes the header part, so that you must substract the fixed header length to deduce the varfield length. Sometimes the length is not counted in bytes but in 16bits words. Sometimes the same lenfield is used by two different varfields. Sometimes the same varfield is referenced by two lenfields, one in bytes one in 16bits words. + + +The length field +~~~~~~~~~~~~~~~~ + +First, a lenfield is declared using ``FieldLenField`` (or a derivate). If its value is None when assembling a packet, its value will be deduced from the varfield that was referenced. The reference is done using either the ``length_of`` parameter or the ``count_of`` parameter. The ``count_of`` parameter has a meaning only when varfield is a field that holds a list (``PacketListField`` or ``FieldListField``). The value will be the name of the varfield, as a string. According to which parameter is used the ``i2len()`` or ``i2count()`` method will be called on the varfield value. The returned value will the be adjusted by the function provided in the adjust parameter. adjust will be applied on 2 arguments: the packet instance and the value returned by ``i2len()`` or ``i2count()``. By default, adjust does nothing:: + + adjust=lambda pkt,x: x + +For instance, if ``the_varfield`` is a list + +:: + + FieldLenField("the_lenfield", None, count_of="the_varfield") + +or if the length is in 16bits words:: + + FieldLenField("the_lenfield", None, length_of="the_varfield", adjust=lambda pkt,x:(x+1)/2) + +The variable length field +~~~~~~~~~~~~~~~~~~~~~~~~~ + +A varfield can be: ``StrLenField``, ``PacketLenField``, ``PacketListField``, ``FieldListField``, ... + +For the two firsts, whe a packet is being dissected, their lengths are deduced from a lenfield already dissected. The link is done using the ``length_from`` parameter, which takes a function that, applied to the partly dissected packet, returns the length in bytes to take for the field. For instance:: + + StrLenField("the_varfield", "the_default_value", length_from = lambda pkt: pkt.the_lenfield) + +or + +:: + + StrLenField("the_varfield", "the_default_value", length_from = lambda pkt: pkt.the_lenfield-12) + +For the ``PacketListField`` and ``FieldListField`` and their derivatives, they work as above when they need a length. If they need a number of elements, the length_from parameter must be ignored and the count_from parameter must be used instead. For instance:: + + FieldListField("the_varfield", ["1.2.3.4"], IPField("", "0.0.0.0"), count_from = lambda pkt: pkt.the_lenfield) + +Examples +^^^^^^^^ + +:: + + class TestSLF(Packet): + fields_desc=[ FieldLenField("len", None, length_of="data"), + StrLenField("data", "", length_from=lambda pkt:pkt.len) ] + + class TestPLF(Packet): + fields_desc=[ FieldLenField("len", None, count_of="plist"), + PacketListField("plist", None, IP, count_from=lambda pkt:pkt.len) ] + + class TestFLF(Packet): + fields_desc=[ + FieldLenField("the_lenfield", None, count_of="the_varfield"), + FieldListField("the_varfield", ["1.2.3.4"], IPField("", "0.0.0.0"), + count_from = lambda pkt: pkt.the_lenfield) ] + + class TestPkt(Packet): + fields_desc = [ ByteField("f1",65), + ShortField("f2",0x4244) ] + def extract_padding(self, p): + return "", p + + class TestPLF2(Packet): + fields_desc = [ FieldLenField("len1", None, count_of="plist",fmt="H", adjust=lambda pkt,x:x+2), + FieldLenField("len2", None, length_of="plist",fmt="I", adjust=lambda pkt,x:(x+1)/2), + PacketListField("plist", None, TestPkt, length_from=lambda x:(x.len2*2)/3*3) ] + +Test the ``FieldListField`` class:: + + >>> TestFLF("\x00\x02ABCDEFGHIJKL") + > + Special ------- From a7bf04f3a7db4c91bacc4058941d4f4f0b271744 Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Sun, 15 Mar 2009 22:10:53 +0100 Subject: [PATCH 35/92] Added ASN.1, SNMP and Automata sections This was taken from Phil's articles on the Wiki. --- advanced_usage.rst | 0 doc/scapy/advanced_usage.rst | 788 +++++++++++++++++++++++++++++++++++ doc/scapy/index.rst | 1 + 3 files changed, 789 insertions(+) create mode 100644 advanced_usage.rst create mode 100644 doc/scapy/advanced_usage.rst diff --git a/advanced_usage.rst b/advanced_usage.rst new file mode 100644 index 000000000..e69de29bb diff --git a/doc/scapy/advanced_usage.rst b/doc/scapy/advanced_usage.rst new file mode 100644 index 000000000..4458e16cb --- /dev/null +++ b/doc/scapy/advanced_usage.rst @@ -0,0 +1,788 @@ +************** +Advanced usage +************** + +ASN.1 and SNMP +============== + +What is ASN.1? +-------------- + +.. note:: + + This is only my view on ASN.1, explained as simply as possible. For more theoretical or academic views, I'm sure you'll find better on the Internet. + +ASN.1 is a notation whose goal is to specify formats for data exchange. It is independant of the way data is encoded. Data encoding is specified in Encoding Rules. + +The most used encoding rules are BER (Basic Encoding Rules) and DER (Distinguished Encoding Rules). Both look the same, but the latter is specified to guarantee uniqueness of encoding. This property is quite interesting when speaking about cryptography, hashes and signatures. + +ASN.1 provides basic objects: integers, many kinds of strings, floats, booleans, containers, etc. They are grouped in the so called Universal class. A given protocol can provide other objects which will be grouped in the Context class. For example, SNMP defines PDU_GET or PDU_SET objects. There are also the Application and Private classes. + +Each of theses objects is given a tag that will be used by the encoding rules. Tags from 1 are used for Universal class. 1 is boolean, 2 is integer, 3 is a bit string, 6 is an OID, 48 is for a sequence. Tags from the ``Context`` class begin at 0xa0. When encountering an object tagged by 0xa0, we'll need to know the context to be able to decode it. For example, in SNMP context, 0xa0 is a PDU_GET object, while in X509 context, it is a container for the certificate version. + +Other objects are created by assembling all those basic brick objects. The composition is done using sequences and arrays (sets) of previously defined or existing objects. The final object (an X509 certificate, a SNMP packet) is a tree whose non-leaf nodes are sequences and sets objects (or derived context objects), and whose leaf nodes are integers, strings, OID, etc. + +Scapy and ASN.1 +--------------- + +Scapy provides a way to easily encode or decode ASN.1 and also program those encoders/decoders. It is quite more lax than what an ASN.1 parser should be, and it kind of ignores constraints. It won't replace neither an ASN.1 parser nor an ASN.1 compiler. Actually, it has been written to be able to encode and decode broken ASN.1. It can handle corrupted encoded strings and can also create those. + +ASN.1 engine +^^^^^^^^^^^^ + +Note: many of the classes definitions presented here use metaclasses. If you don't look precisely at the source code and you only rely on my captures, you may think they sometimes exhibit a kind of magic behaviour. +`` +Scapy ASN.1 engine provides classes to link objects and their tags. They inherit from the ``ASN1_Class``. The first one is ``ASN1_Class_UNIVERSAL``, which provide tags for most Universal objects. Each new context (``SNMP``, ``X509``) will inherit from it and add its own objects. + +:: + + class ASN1_Class_UNIVERSAL(ASN1_Class): + name = "UNIVERSAL" + # [...] + BOOLEAN = 1 + INTEGER = 2 + BIT_STRING = 3 + # [...] + + class ASN1_Class_SNMP(ASN1_Class_UNIVERSAL): + name="SNMP" + PDU_GET = 0xa0 + PDU_NEXT = 0xa1 + PDU_RESPONSE = 0xa2 + + class ASN1_Class_X509(ASN1_Class_UNIVERSAL): + name="X509" + CONT0 = 0xa0 + CONT1 = 0xa1 + # [...] + +All ASN.1 objects are represented by simple Python instances that act as nutshells for the raw values. The simple logic is handled by ``ASN1_Object`` whose they inherit from. Hence they are quite simple:: + + class ASN1_INTEGER(ASN1_Object): + tag = ASN1_Class_UNIVERSAL.INTEGER + + class ASN1_STRING(ASN1_Object): + tag = ASN1_Class_UNIVERSAL.STRING + + class ASN1_BIT_STRING(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.BIT_STRING + +These instances can be assembled to create an ASN.1 tree:: + + >>> x=ASN1_SEQUENCE([ASN1_INTEGER(7),ASN1_STRING("egg"),ASN1_SEQUENCE([ASN1_BOOLEAN(False)])]) + >>> x + , , ]]>]]> + >>> x.show() + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + + +Encoding engines +^^^^^^^^^^^^^^^^^ + +As with the standard, ASN.1 and encoding are independent. We have just seen how to create a compounded ASN.1 object. To encode or decode it, we need to choose an encoding rule. Scapy provides only BER for the moment (actually, it may be DER. DER looks like BER except only minimal encoding is authorised which may well be what I did). I call this an ASN.1 codec. + +Encoding and decoding are done using class methods provided by the codec. For example the ``BERcodec_INTEGER`` class provides a ``.enc()`` and a ``.dec()`` class methods that can convert between an encoded string and a value of their type. They all inherit from BERcodec_Object which is able to decode objects from any type:: + + >>> BERcodec_INTEGER.enc(7) + '\x02\x01\x07' + >>> BERcodec_BIT_STRING.enc("egg") + '\x03\x03egg' + >>> BERcodec_STRING.enc("egg") + '\x04\x03egg' + >>> BERcodec_STRING.dec('\x04\x03egg') + (, '') + >>> BERcodec_STRING.dec('\x03\x03egg') + Traceback (most recent call last): + File "", line 1, in ? + File "/usr/bin/scapy", line 2099, in dec + return cls.do_dec(s, context, safe) + File "/usr/bin/scapy", line 2178, in do_dec + l,s,t = cls.check_type_check_len(s) + File "/usr/bin/scapy", line 2076, in check_type_check_len + l,s3 = cls.check_type_get_len(s) + File "/usr/bin/scapy", line 2069, in check_type_get_len + s2 = cls.check_type(s) + File "/usr/bin/scapy", line 2065, in check_type + (cls.__name__, ord(s[0]), ord(s[0]),cls.tag), remaining=s) + BER_BadTag_Decoding_Error: BERcodec_STRING: Got tag [3/0x3] while expecting + ### Already decoded ### + None + ### Remaining ### + '\x03\x03egg' + >>> BERcodec_Object.dec('\x03\x03egg') + (, '') + +ASN.1 objects are encoded using their ``.enc()`` method. This method must be called with the codec we want to use. All codecs are referenced in the ASN1_Codecs object. str() can also be used. In this case, the default codec (``conf.ASN1_default_codec``) will be used. + +:: + + >>> x.enc(ASN1_Codecs.BER) + '0\r\x02\x01\x07\x04\x03egg0\x03\x01\x01\x00' + >>> str(x) + '0\r\x02\x01\x07\x04\x03egg0\x03\x01\x01\x00' + >>> xx,remain = BERcodec_Object.dec(_) + >>> xx.show() + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + + + >>> remain + '' + +By default, decoding is done using the ``Universal`` class, which means objects defined in the ``Context`` class will not be decoded. There is a good reason for that: the decoding depends on the context! + +:: + + >>> cert=""" + ... MIIF5jCCA86gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMC + ... VVMxHTAbBgNVBAoTFEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNB + ... bWVyaWNhIE9ubGluZSBJbmMuMTcwNQYDVQQDEy5BT0wgVGltZSBXYXJuZXIg + ... Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyOTA2MDAw + ... MFoXDTM3MDkyODIzNDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRB + ... T0wgVGltZSBXYXJuZXIgSW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUg + ... SW5jLjE3MDUGA1UEAxMuQU9MIFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNh + ... dGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC + ... ggIBALQ3WggWmRToVbEbJGv8x4vmh6mJ7ouZzU9AhqS2TcnZsdw8TQ2FTBVs + ... RotSeJ/4I/1n9SQ6aF3Q92RhQVSji6UI0ilbm2BPJoPRYxJWSXakFsKlnUWs + ... i4SVqBax7J/qJBrvuVdcmiQhLE0OcR+mrF1FdAOYxFSMFkpBd4aVdQxHAWZg + ... /BXxD+r1FHjHDtdugRxev17nOirYlxcwfACtCJ0zr7iZYYCLqJV+FNwSbKTQ + ... 2O9ASQI2+W6p1h2WVgSysy0WVoaP2SBXgM1nEG2wTPDaRrbqJS5Gr42whTg0 + ... ixQmgiusrpkLjhTXUr2eacOGAgvqdnUxCc4zGSGFQ+aJLZ8lN2fxI2rSAG2X + ... +Z/nKcrdH9cG6rjJuQkhn8g/BsXS6RJGAE57COtCPStIbp1n3UsC5ETzkxml + ... J85per5n0/xQpCyrw2u544BMzwVhSyvcG7mm0tCq9Stz+86QNZ8MUhy/XCFh + ... EVsVS6kkUfykXPcXnbDS+gfpj1bkGoxoigTTfFrjnqKhynFbotSg5ymFXQNo + ... Kk/SBtc9+cMDLz9l+WceR0DTYw/j1Y75hauXTLPXJuuWCpTehTacyH+BCQJJ + ... Kg71ZDIMgtG6aoIbs0t0EfOMd9afv9w3pKdVBC/UMejTRrkDfNoSTllkt1Ex + ... MVCgyhwn2RAurda9EGYrw7AiShJbAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMB + ... Af8wHQYDVR0OBBYEFE9pbQN+nZ8HGEO8txBO1b+pxCAoMB8GA1UdIwQYMBaA + ... FE9pbQN+nZ8HGEO8txBO1b+pxCAoMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG + ... 9w0BAQUFAAOCAgEAO/Ouyuguh4X7ZVnnrREUpVe8WJ8kEle7+z802u6teio0 + ... cnAxa8cZmIDJgt43d15Ui47y6mdPyXSEkVYJ1eV6moG2gcKtNuTxVBFT8zRF + ... ASbI5Rq8NEQh3q0l/HYWdyGQgJhXnU7q7C+qPBR7V8F+GBRn7iTGvboVsNIY + ... vbdVgaxTwOjdaRITQrcCtQVBynlQboIOcXKTRuidDV29rs4prWPVVRaAMCf/ + ... drr3uNZK49m1+VLQTkCpx+XCMseqdiThawVQ68W/ClTluUI8JPu3B5wwn3la + ... 5uBAUhX0/Kr0VvlEl4ftDmVyXr4m+02kLQgH3thcoNyBM5kYJRF3p+v9WAks + ... mWsbivNSPxpNSGDxoPYzAlOL7SUJuA0t7Zdz7NeWH45gDtoQmy8YJPamTQr5 + ... O8t1wswvziRpyQoijlmn94IM19drNZxDAGrElWe6nEXLuA4399xOAU++CrYD + ... 062KRffaJ00psUjf5BHklka9bAI+1lHIlRcBFanyqqryvy9lG2/QuRqT9Y41 + ... xICHPpQvZuTpqP9BnHAqTyo5GJUefvthATxRCC4oGKQWDzH9OmwjkyB24f0H + ... hdFbP9IcczLd+rn4jM8Ch3qaluTtT4mNU0OrDhPAARW0eTjb/G49nlG2uBOL + ... Z8/5fNkiHfZdxRwBL5joeiQYvITX+txyW/fBOmg= + ... """.decode("base64") + >>> (dcert,remain) = BERcodec_Object.dec(cert) + Traceback (most recent call last): + File "", line 1, in ? + File "/usr/bin/scapy", line 2099, in dec + return cls.do_dec(s, context, safe) + File "/usr/bin/scapy", line 2094, in do_dec + return codec.dec(s,context,safe) + File "/usr/bin/scapy", line 2099, in dec + return cls.do_dec(s, context, safe) + File "/usr/bin/scapy", line 2218, in do_dec + o,s = BERcodec_Object.dec(s, context, safe) + File "/usr/bin/scapy", line 2099, in dec + return cls.do_dec(s, context, safe) + File "/usr/bin/scapy", line 2094, in do_dec + return codec.dec(s,context,safe) + File "/usr/bin/scapy", line 2099, in dec + return cls.do_dec(s, context, safe) + File "/usr/bin/scapy", line 2218, in do_dec + o,s = BERcodec_Object.dec(s, context, safe) + File "/usr/bin/scapy", line 2099, in dec + return cls.do_dec(s, context, safe) + File "/usr/bin/scapy", line 2092, in do_dec + raise BER_Decoding_Error("Unknown prefix [%02x] for [%r]" % (p,t), remaining=s) + BER_Decoding_Error: Unknown prefix [a0] for ['\xa0\x03\x02\x01\x02\x02\x01\x010\r\x06\t*\x86H...'] + ### Already decoded ### + [[]] + ### Remaining ### + '\xa0\x03\x02\x01\x02\x02\x01\x010\r\x06\t*\x86H\x86\xf7\r\x01\x01\x05\x05\x000\x81\x831\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x1d0\x1b\x06\x03U\x04\n\x13\x14AOL Time Warner Inc.1\x1c0\x1a\x06\x03U\x04\x0b\x13\x13America Online Inc.1705\x06\x03U\x04\x03\x13.AOL Time Warner Root Certification Authority 20\x1e\x17\r020529060000Z\x17\r370928234300Z0\x81\x831\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x1d0\x1b\x06\x03U\x04\n\x13\x14AOL Time Warner Inc.1\x1c0\x1a\x06\x03U\x04\x0b\x13\x13America Online Inc.1705\x06\x03U\x04\x03\x13.AOL Time Warner Root Certification Authority 20\x82\x02"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x02\x0f\x000\x82\x02\n\x02\x82\x02\x01\x00\xb47Z\x08\x16\x99\x14\xe8U\xb1\x1b$k\xfc\xc7\x8b\xe6\x87\xa9\x89\xee\x8b\x99\xcdO@\x86\xa4\xb6M\xc9\xd9\xb1\xdc\xd6Q\xc8\x95\x17\x01\x15\xa9\xf2\xaa\xaa\xf2\xbf/e\x1bo\xd0\xb9\x1a\x93\xf5\x8e5\xc4\x80\x87>\x94/f\xe4\xe9\xa8\xffA\x9cp*O*9\x18\x95\x1e~\xfba\x01>> (dcert,remain) = BERcodec_Object.dec(cert, context=ASN1_Class_X509) + >>> dcert.show() + # ASN1_SEQUENCE: + # ASN1_SEQUENCE: + # ASN1_X509_CONT0: + + + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + # ASN1_SEQUENCE: + + + + # ASN1_X509_CONT3: + # ASN1_SEQUENCE: + # ASN1_SEQUENCE: + + + + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + + + + # ASN1_SEQUENCE: + + + \xd6Q\xc8\x95\x17\x01\x15\xa9\xf2\xaa\xaa\xf2\xbf/e\x1bo\xd0\xb9\x1a\x93\xf5\x8e5\xc4\x80\x87>\x94/f\xe4\xe9\xa8\xffA\x9cp*O*9\x18\x95\x1e~\xfba\x01 + +ASN.1 layers +^^^^^^^^^^^^ + +While this may be nice, it's only an ASN.1 encoder/decoder. Nothing related to Scapy yet. + +ASN.1 fields +~~~~~~~~~~~~ + +Scapy provides ASN.1 fields. They will wrap ASN.1 objects and provide the necessary logic to bind a field name to the value. ASN.1 packets will be described as a tree of ASN.1 fields. Then each field name will be made available as a normal ``Packet`` object, in a flat flavor (ex: to access the version field of a SNMP packet, you don't need to know how many containers wrap it). + +Each ASN.1 field is linked to an ASN.1 object through its tag. + + +ASN.1 packets +~~~~~~~~~~~~~ + +ASN.1 packets inherit from the Packet class. Instead of a ``fields_desc`` list of fields, they define ``ASN1_codec`` and ``ASN1_root`` attributes. The first one is a codec (for example: ``ASN1_Codecs.BER``), the second one is a tree compounded with ASN.1 fields. + +A complete example: SNMP +------------------------ + +SNMP defines new ASN.1 objects. We need to define them:: + + class ASN1_Class_SNMP(ASN1_Class_UNIVERSAL): + name="SNMP" + PDU_GET = 0xa0 + PDU_NEXT = 0xa1 + PDU_RESPONSE = 0xa2 + PDU_SET = 0xa3 + PDU_TRAPv1 = 0xa4 + PDU_BULK = 0xa5 + PDU_INFORM = 0xa6 + PDU_TRAPv2 = 0xa7 + +These objects are PDU, and are in fact new names for a sequence container (this is generally the case for context objects: they are old containers with new names). This means creating the corresponding ASN.1 objects and BER codecs is simplistic:: + + class ASN1_SNMP_PDU_GET(ASN1_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_GET + + class ASN1_SNMP_PDU_NEXT(ASN1_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_NEXT + + # [...] + + class BERcodec_SNMP_PDU_GET(BERcodec_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_GET + + class BERcodec_SNMP_PDU_NEXT(BERcodec_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_NEXT + + # [...] + +Metaclasses provide the magic behind the fact that everything is automatically registered and that ASN.1 objects and BER codecs can find each other. + +The ASN.1 fields are also trivial:: + + class ASN1F_SNMP_PDU_GET(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_SNMP.PDU_GET + + class ASN1F_SNMP_PDU_NEXT(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_SNMP.PDU_NEXT + + # [...] + +Now, the hard part, the ASN.1 packet:: + + SNMP_error = { 0: "no_error", + 1: "too_big", + # [...] + } + + SNMP_trap_types = { 0: "cold_start", + 1: "warm_start", + # [...] + } + + class SNMPvarbind(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("oid","1.3"), + ASN1F_field("value",ASN1_NULL(0)) + ) + + + class SNMPget(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SNMP_PDU_GET( ASN1F_INTEGER("id",0), + ASN1F_enum_INTEGER("error",0, SNMP_error), + ASN1F_INTEGER("error_index",0), + ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) + ) + + class SNMPnext(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SNMP_PDU_NEXT( ASN1F_INTEGER("id",0), + ASN1F_enum_INTEGER("error",0, SNMP_error), + ASN1F_INTEGER("error_index",0), + ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) + ) + # [...] + + class SNMP(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("version", 1, {0:"v1", 1:"v2c", 2:"v2", 3:"v3"}), + ASN1F_STRING("community","public"), + ASN1F_CHOICE("PDU", SNMPget(), + SNMPget, SNMPnext, SNMPresponse, SNMPset, + SNMPtrapv1, SNMPbulk, SNMPinform, SNMPtrapv2) + ) + def answers(self, other): + return ( isinstance(self.PDU, SNMPresponse) and + ( isinstance(other.PDU, SNMPget) or + isinstance(other.PDU, SNMPnext) or + isinstance(other.PDU, SNMPset) ) and + self.PDU.id == other.PDU.id ) + # [...] + bind_layers( UDP, SNMP, sport=161) + bind_layers( UDP, SNMP, dport=161) + +That wasn't that much difficult. If you think that can't be that short to implement SNMP encoding/decoding and that I may may have cut too much, just look at the complete source code. + +Now, how to use it? As usual:: + + >>> a=SNMP(version=3, PDU=SNMPget(varbindlist=[SNMPvarbind(oid="1.2.3",value=5), + ... SNMPvarbind(oid="3.2.1",value="hello")])) + >>> a.show() + ###[ SNMP ]### + version= v3 + community= 'public' + \PDU\ + |###[ SNMPget ]### + | id= 0 + | error= no_error + | error_index= 0 + | \varbindlist\ + | |###[ SNMPvarbind ]### + | | oid= '1.2.3' + | | value= 5 + | |###[ SNMPvarbind ]### + | | oid= '3.2.1' + | | value= 'hello' + >>> hexdump(a) + 0000 30 2E 02 01 03 04 06 70 75 62 6C 69 63 A0 21 02 0......public.!. + 0010 01 00 02 01 00 02 01 00 30 16 30 07 06 02 2A 03 ........0.0...*. + 0020 02 01 05 30 0B 06 02 7A 01 04 05 68 65 6C 6C 6F ...0...z...hello + >>> send(IP(dst="1.2.3.4")/UDP()/SNMP()) + . + Sent 1 packets. + >>> SNMP(str(a)).show() + ###[ SNMP ]### + version= + community= + \PDU\ + |###[ SNMPget ]### + | id= + | error= + | error_index= + | \varbindlist\ + | |###[ SNMPvarbind ]### + | | oid= + | | value= + | |###[ SNMPvarbind ]### + | | oid= + | | value= + + + +Resolving OID from a MIB +------------------------ + +About OID objects +^^^^^^^^^^^^^^^^^ + +OID objects are created with an ``ASN1_OID`` class:: + + >>> o1=ASN1_OID("2.5.29.10") + >>> o2=ASN1_OID("1.2.840.113549.1.1.1") + >>> o1,o2 + (, ) + +Loading a MIB +^^^^^^^^^^^^^ + +Scapy can parse MIB files and become aware of a mapping between an OID and its name:: + + >>> load_mib("mib/*") + >>> o1,o2 + (, ) + +The MIB files I've used are attached to this page. + +Scapy's MIB database +^^^^^^^^^^^^^^^^^^^^ + +All MIB information is stored into the conf.mib object. This object can be used to find the OID of a name + +:: + + >>> conf.mib.sha1_with_rsa_signature + '1.2.840.113549.1.1.5' + +or to resolve an OID:: + + >>> conf.mib._oidname("1.2.3.6.1.4.1.5") + 'enterprises.5' + +It is even possible to graph it:: + + >>> conf.mib._make_graph() + + + +Automata +======== + +Scapy enables to create esaily network automata. Scapy does not stick to a specific model like Moore or Mealy automata. It provides a flexible way for you to choose you way to go. + +An automaton in Scapy is deterministic. It has different states. A start state and some end and error states. There are transitions from one state to another. Transitions can be transitions on a specific condition, transitions on the reception of a specific packet or transitions on a timeout. When a transition is taken, one or more actions can be run. An action can be bound to many transitions. Parameters can be passed from states to transitions and from transitions to states and actions. + +From a programmer's point of view, states, transitions and actions are methods from an Automaton subclass. They are decorated to provide meta-information needed in order for the automaton to work. + +First example +------------- + +Let's begin with a simple example. I take the convention to write states with capitals, but anything valid with python syntax would work as well. + +:: + + class HelloWorld(Automaton): + @ATMT.state(initial=1) + def BEGIN(self): + print "State=BEGIN" + + @ATMT.condition(BEGIN) + def wait_for_nothing(self): + print "Wait for nothing..." + raise self.END() + + @ATMT.action(wait_for_nothing) + def on_nothing(self): + print "Action on 'nothing' condition" + + @ATMT.state(final=1) + def END(self): + print "State=END" + +In this example, we can see 3 decorators: + + * ATMT.state that is used to indicate that a method is a state, and that can have initial, final and error optional arguments set to non-zero for special states. + * ATMT.condition that indicate a method to be run when the automaton state reaches the indicated state. The argument is the name of the method representing that state + * ATMT.action binds a method to a transition and is run when the transition is taken. + +Running this example gives the following result:: + + >>> a=HelloWorld() + >>> a.run() + State=BEGIN + Wait for nothing... + Action on 'nothing' condition + State=END + +This simple automaton can be described with the following graph: + + + +The graph can be automatically drawn from the code with:: + + >>> HelloWorld.graph() + +Changing states +--------------- + +The ``ATMT.state`` decorator transforms a method into a function that returns an exception. If you raise that exception, the automaton state will be changed. If the change occurs in a transition, actions bound to this transition will be called. The parameters given to the function replacing the method will be kept and finally delivered to the method. The exception has a method action_parameters that can be called before it is raised so that it will store parameters to be delivered to all actions bound to the current transition. + +As an example, let's consider the following state:: + + @ATMT.state() + def MY_STATE(self, param1, param2): + print "state=MY_STATE. param1=%r param2=%r" % (param1, param2) + +This state will be reached with the following code:: + + @ATMT.receive_condition(ANOTHER_STATE) + def received_ICMP(self, pkt): + if ICMP in pkt: + raise self.MY_STATE("got icmp", pkt[ICMP].type) + +Let's suppose we want to bind an action to this transition, that will also need some parameters:: + + @ATMT.action(received_ICMP) + def on_ICMP(self, icmp_type, icmp_code): + self.retaliate(icmp_type, icmp_code) + +The condition should become:: + + @ATMT.receive_condition(ANOTHER_STATE) + def received_ICMP(self, pkt): + if ICMP in pkt: + raise self.MY_STATE("got icmp", pkt[ICMP].type).action_parameters(pkt[ICMP].type, pkt[ICMP].code) + +Real example +------------ + +Here is a real example take from Scapy. It implements a TFTP client that can issue read requests. + +:: + + class TFTP_read(Automaton): + def parse_args(self, filename, server, sport = None, port=69, **kargs): + Automaton.parse_args(self, **kargs) + self.filename = filename + self.server = server + self.port = port + self.sport = sport + + def master_filter(self, pkt): + return ( IP in pkt and pkt[IP].src == self.server and UDP in pkt + and pkt[UDP].dport == self.my_tid + and (self.server_tid is None or pkt[UDP].sport == self.server_tid) ) + + # BEGIN + @ATMT.state(initial=1) + def BEGIN(self): + self.blocksize=512 + self.my_tid = self.sport or RandShort()._fix() + bind_bottom_up(UDP, TFTP, dport=self.my_tid) + self.server_tid = None + self.res = "" + + self.l3 = IP(dst=self.server)/UDP(sport=self.my_tid, dport=self.port)/TFTP() + self.last_packet = self.l3/TFTP_RRQ(filename=self.filename, mode="octet") + self.send(self.last_packet) + self.awaiting=1 + + raise self.WAITING() + + # WAITING + @ATMT.state() + def WAITING(self): + pass + + @ATMT.receive_condition(WAITING) + def receive_data(self, pkt): + if TFTP_DATA in pkt and pkt[TFTP_DATA].block == self.awaiting: + if self.server_tid is None: + self.server_tid = pkt[UDP].sport + self.l3[UDP].dport = self.server_tid + raise self.RECEIVING(pkt) + @ATMT.action(receive_data) + def send_ack(self): + self.last_packet = self.l3 / TFTP_ACK(block = self.awaiting) + self.send(self.last_packet) + + @ATMT.receive_condition(WAITING, prio=1) + def receive_error(self, pkt): + if TFTP_ERROR in pkt: + raise self.ERROR(pkt) + + @ATMT.timeout(WAITING, 3) + def timeout_waiting(self): + raise self.WAITING() + @ATMT.action(timeout_waiting) + def retransmit_last_packet(self): + self.send(self.last_packet) + + # RECEIVED + @ATMT.state() + def RECEIVING(self, pkt): + recvd = pkt[Raw].load + self.res += recvd + self.awaiting += 1 + if len(recvd) == self.blocksize: + raise self.WAITING() + raise self.END() + + # ERROR + @ATMT.state(error=1) + def ERROR(self,pkt): + split_bottom_up(UDP, TFTP, dport=self.my_tid) + return pkt[TFTP_ERROR].summary() + + #END + @ATMT.state(final=1) + def END(self): + split_bottom_up(UDP, TFTP, dport=self.my_tid) + return self.res + +It can be run like this, for instance:: + + >>> TFTP_read("my_file", "192.168.1.128").run() + +Detailed documentation +---------------------- + +Decorators +^^^^^^^^^^ +Decorator for states +~~~~~~~~~~~~~~~~~~~~ + +States are methods decorated by the result of the ``ATMT.state`` function. It can take 3 optional parameters, ``initial``, ``final`` and ``error``, that, when set to ``True``, indicate that the state is an initial, final or error state. + +:: + + class Example(Automaton): + @ATMT.state(initial=1) + def BEGIN(self): + pass + + @ATMT.state() + def SOME_STATE(self) + pass + + @ATMT.state(final=1) + def END(self): + return "Result of the automaton: 42" + + @ATMT.state(error=1) + def ERROR(self): + return "Partial result, or explanation" + # [...] + +Decorators for transitions +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Transitions are methods decorated by the result of one of ``ATMT.condition``, ``ATMT.receive_condition``, ``ATMT.timeout``. They all take as argument the state method they are related to. ATMT.timeout also have a mandatory ``timeout`` parameter to provide the timeout value in seconds. ``ATMT.condition`` and ``ATMT.receive_condition`` have an optional ``prio`` parameter so that the order in which conditions are evaluated can be forced. Default priority is 0. Transitions with the same priority level are called in an undetermined order. + +When the automaton switches to a given state, the state's method is executed. Then transitions methods are called at specific moments until one triggers a new state (something like ``raise self.MY_NEW_STATE()``). First, right after the state's method returns, the ``ATMT.condition`` decorated methods are run by growing prio. Then each time a packet is received and accepted by the master filter all ``ATMT.receive_condition`` decorated hods are called by growing prio. When a timeout is reached since the time we entered into the current space, the corresponding ``ATMT.timeout`` decorated method is called. + +:: + + class Example(Automaton): + @ATMT.state() + def WAITING(self): + pass + + @ATMT.condition(WAITING) + def it_is_raining(self): + if not self.have_umbrella: + raise self.ERROR_WET() + + @ATMT.receive_condition(WAITING, prio=1) + def it_is_ICMP(self, pkt): + if ICMP in pkt: + raise self.RECEIVED_ICMP(pkt) + + @ATMT.receive_condition(WAITING, prio=2) + def it_is_IP(self, pkt): + if IP in pkt: + raise self.RECEIVED_IP(pkt) + + @ATMT.timeout(WAITING, 10.0) + def waiting_timeout(self): + raise self.ERROR_TIMEOUT() + +Decorator for actions +~~~~~~~~~~~~~~~~~~~~~ + +Actions are methods that are decorated by the return of ``ATMT.action`` function. This function takes the transition method it is bound to as first parameter and an optionnal priority ``prio`` as a second parameter. Default priority is 0. An action method can be decorated many times to be bound to many transitions. + +:: + + class Example(Automaton): + @ATMT.state(initial=1) + def BEGIN(self): + pass + + @ATMT.state(final=1) + def END(self): + pass + + @ATMT.condition(BEGIN, prio=1) + def maybe_go_to_end(self): + if random() > 0.5: + raise self.END() + @ATMT.condition(BEGIN, prio=2) + def certainly_go_to_end(self): + raise self.END() + + @ATMT.action(maybe_go_to_end) + def maybe_action(self): + print "We are lucky..." + @ATMT.action(certainly_go_to_end) + def certainly_action(self): + print "We are not lucky..." + @ATMT.action(maybe_go_to_end, prio=1) + @ATMT.action(certainly_go_to_end, prio=1) + def always_action(self): + print "This wasn't luck!..." + +The two possible outputs are:: + + >>> a=Example() + >>> a.run() + We are not lucky... + This wasn't luck!... + >>> a.run() + We are lucky... + This wasn't luck!... + +Methods to overload +^^^^^^^^^^^^^^^^^^^ + +Two methods are hooks to be overloaded. + +The ``parse_args()`` method is called with arguments given at ``__init__()`` and ``run()``. Use that to parametrize the behaviour of your automaton. + +The ``master_filter()` method is called each time a packet is sniffed and decides if it is interesting for the automaton. When working on a specific protocol, this is where you will ensure the packet belongs to the connection you are being part of, so that you do not need to make all the sanity checks in each transition. + diff --git a/doc/scapy/index.rst b/doc/scapy/index.rst index ef319da40..bed746875 100644 --- a/doc/scapy/index.rst +++ b/doc/scapy/index.rst @@ -18,6 +18,7 @@ This document is under a `Creative Commons Attribution - Non-Commercial installation usage + advanced_usage extending troubleshooting From 6070e6ac1bc1f5c94616658f0110665a39667b83 Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Sun, 15 Mar 2009 22:13:35 +0100 Subject: [PATCH 36/92] Reordered table of contents --- doc/scapy/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/scapy/index.rst b/doc/scapy/index.rst index bed746875..b15473927 100644 --- a/doc/scapy/index.rst +++ b/doc/scapy/index.rst @@ -20,9 +20,9 @@ This document is under a `Creative Commons Attribution - Non-Commercial usage advanced_usage extending - troubleshooting - build_dissect + + troubleshooting development backmatter From faf94920b0b982dd573ba7ee47ee838950342290 Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Sun, 15 Mar 2009 22:29:59 +0100 Subject: [PATCH 37/92] Added graphics for automata examples --- doc/scapy/advanced_usage.rst | 6 ++++-- graphics/ATMT_HelloWorld.png | Bin 0 -> 7222 bytes graphics/ATMT_TFTP_read.png | Bin 0 -> 31887 bytes 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 graphics/ATMT_HelloWorld.png create mode 100644 graphics/ATMT_TFTP_read.png diff --git a/doc/scapy/advanced_usage.rst b/doc/scapy/advanced_usage.rst index 4458e16cb..c15f4622f 100644 --- a/doc/scapy/advanced_usage.rst +++ b/doc/scapy/advanced_usage.rst @@ -541,7 +541,7 @@ Running this example gives the following result:: This simple automaton can be described with the following graph: - +.. image:: graphics/ATMT_HelloWorld.* The graph can be automatically drawn from the code with:: @@ -583,6 +583,8 @@ Real example Here is a real example take from Scapy. It implements a TFTP client that can issue read requests. +.. image:: graphics/ATMT_TFTP_read.* + :: class TFTP_read(Automaton): @@ -784,5 +786,5 @@ Two methods are hooks to be overloaded. The ``parse_args()`` method is called with arguments given at ``__init__()`` and ``run()``. Use that to parametrize the behaviour of your automaton. -The ``master_filter()` method is called each time a packet is sniffed and decides if it is interesting for the automaton. When working on a specific protocol, this is where you will ensure the packet belongs to the connection you are being part of, so that you do not need to make all the sanity checks in each transition. +The ``master_filter()`` method is called each time a packet is sniffed and decides if it is interesting for the automaton. When working on a specific protocol, this is where you will ensure the packet belongs to the connection you are being part of, so that you do not need to make all the sanity checks in each transition. diff --git a/graphics/ATMT_HelloWorld.png b/graphics/ATMT_HelloWorld.png new file mode 100644 index 0000000000000000000000000000000000000000..e5b5ddb188a64bba6f71d07ff850d4f9601a0ce5 GIT binary patch literal 7222 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 literal 0 HcmV?d00001 diff --git a/graphics/ATMT_TFTP_read.png b/graphics/ATMT_TFTP_read.png new file mode 100644 index 0000000000000000000000000000000000000000..50621d976ac1addc852537ac49343ffd0e9e7209 GIT binary patch literal 31887 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 z Date: Sun, 15 Mar 2009 22:32:06 +0100 Subject: [PATCH 38/92] Fixed typos and layout --- doc/scapy/advanced_usage.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/scapy/advanced_usage.rst b/doc/scapy/advanced_usage.rst index c15f4622f..5b4d0b9b3 100644 --- a/doc/scapy/advanced_usage.rst +++ b/doc/scapy/advanced_usage.rst @@ -689,7 +689,7 @@ States are methods decorated by the result of the ``ATMT.state`` function. It ca pass @ATMT.state() - def SOME_STATE(self) + def SOME_STATE(self): pass @ATMT.state(final=1) @@ -782,9 +782,9 @@ The two possible outputs are:: Methods to overload ^^^^^^^^^^^^^^^^^^^ -Two methods are hooks to be overloaded. +Two methods are hooks to be overloaded: -The ``parse_args()`` method is called with arguments given at ``__init__()`` and ``run()``. Use that to parametrize the behaviour of your automaton. +* The ``parse_args()`` method is called with arguments given at ``__init__()`` and ``run()``. Use that to parametrize the behaviour of your automaton. -The ``master_filter()`` method is called each time a packet is sniffed and decides if it is interesting for the automaton. When working on a specific protocol, this is where you will ensure the packet belongs to the connection you are being part of, so that you do not need to make all the sanity checks in each transition. +* The ``master_filter()`` method is called each time a packet is sniffed and decides if it is interesting for the automaton. When working on a specific protocol, this is where you will ensure the packet belongs to the connection you are being part of, so that you do not need to make all the sanity checks in each transition. From ac4aaa1630332d347667fabed8a6d34a605d0115 Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Sun, 15 Mar 2009 23:24:32 +0100 Subject: [PATCH 39/92] Fixed more typos and minor layout issues --- doc/scapy/advanced_usage.rst | 16 +++++++++------- doc/scapy/build_dissect.rst | 33 +++++++++++++++++---------------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/doc/scapy/advanced_usage.rst b/doc/scapy/advanced_usage.rst index 5b4d0b9b3..0ae219996 100644 --- a/doc/scapy/advanced_usage.rst +++ b/doc/scapy/advanced_usage.rst @@ -115,7 +115,7 @@ Encoding and decoding are done using class methods provided by the codec. For ex >>> BERcodec_Object.dec('\x03\x03egg') (, '') -ASN.1 objects are encoded using their ``.enc()`` method. This method must be called with the codec we want to use. All codecs are referenced in the ASN1_Codecs object. str() can also be used. In this case, the default codec (``conf.ASN1_default_codec``) will be used. +ASN.1 objects are encoded using their ``.enc()`` method. This method must be called with the codec we want to use. All codecs are referenced in the ASN1_Codecs object. ``str()`` can also be used. In this case, the default codec (``conf.ASN1_default_codec``) will be used. :: @@ -493,7 +493,7 @@ It is even possible to graph it:: Automata ======== -Scapy enables to create esaily network automata. Scapy does not stick to a specific model like Moore or Mealy automata. It provides a flexible way for you to choose you way to go. +Scapy enables to create easily network automata. Scapy does not stick to a specific model like Moore or Mealy automata. It provides a flexible way for you to choose you way to go. An automaton in Scapy is deterministic. It has different states. A start state and some end and error states. There are transitions from one state to another. Transitions can be transitions on a specific condition, transitions on the reception of a specific packet or transitions on a timeout. When a transition is taken, one or more actions can be run. An action can be bound to many transitions. Parameters can be passed from states to transitions and from transitions to states and actions. @@ -502,7 +502,7 @@ From a programmer's point of view, states, transitions and actions are methods f First example ------------- -Let's begin with a simple example. I take the convention to write states with capitals, but anything valid with python syntax would work as well. +Let's begin with a simple example. I take the convention to write states with capitals, but anything valid with Python syntax would work as well. :: @@ -526,9 +526,11 @@ Let's begin with a simple example. I take the convention to write states with ca In this example, we can see 3 decorators: - * ATMT.state that is used to indicate that a method is a state, and that can have initial, final and error optional arguments set to non-zero for special states. - * ATMT.condition that indicate a method to be run when the automaton state reaches the indicated state. The argument is the name of the method representing that state - * ATMT.action binds a method to a transition and is run when the transition is taken. +* ``ATMT.state`` that is used to indicate that a method is a state, and that can + have initial, final and error optional arguments set to non-zero for special states. +* ``ATMT.condition`` that indicate a method to be run when the automaton state + reaches the indicated state. The argument is the name of the method representing that state +* ``ATMT.action` binds a method to a transition and is run when the transition is taken. Running this example gives the following result:: @@ -704,7 +706,7 @@ States are methods decorated by the result of the ``ATMT.state`` function. It ca Decorators for transitions ~~~~~~~~~~~~~~~~~~~~~~~~~~ -Transitions are methods decorated by the result of one of ``ATMT.condition``, ``ATMT.receive_condition``, ``ATMT.timeout``. They all take as argument the state method they are related to. ATMT.timeout also have a mandatory ``timeout`` parameter to provide the timeout value in seconds. ``ATMT.condition`` and ``ATMT.receive_condition`` have an optional ``prio`` parameter so that the order in which conditions are evaluated can be forced. Default priority is 0. Transitions with the same priority level are called in an undetermined order. +Transitions are methods decorated by the result of one of ``ATMT.condition``, ``ATMT.receive_condition``, ``ATMT.timeout``. They all take as argument the state method they are related to. ``ATMT.timeout`` also have a mandatory ``timeout`` parameter to provide the timeout value in seconds. ``ATMT.condition`` and ``ATMT.receive_condition`` have an optional ``prio`` parameter so that the order in which conditions are evaluated can be forced. Default priority is 0. Transitions with the same priority level are called in an undetermined order. When the automaton switches to a given state, the state's method is executed. Then transitions methods are called at specific moments until one triggers a new state (something like ``raise self.MY_NEW_STATE()``). First, right after the state's method returns, the ``ATMT.condition`` decorated methods are run by growing prio. Then each time a packet is received and accepted by the master filter all ``ATMT.receive_condition`` decorated hods are called by growing prio. When a timeout is reached since the time we entered into the current space, the corresponding ``ATMT.timeout`` decorated method is called. diff --git a/doc/scapy/build_dissect.rst b/doc/scapy/build_dissect.rst index 79287417f..f621e7cac 100644 --- a/doc/scapy/build_dissect.rst +++ b/doc/scapy/build_dissect.rst @@ -72,7 +72,7 @@ organized. >>> p.summary() 'IP / TCP 127.0.0.1:ftp-data > 127.0.0.1:www S / Raw' -We are interested in 2 "inside" fields of the class Packet: +We are interested in 2 "inside" fields of the class ``Packet``: * ``p.underlayer`` * ``p.payload`` @@ -312,7 +312,7 @@ dissected. ``self`` points to the current layer. dissected as "``Raw``" data (which is some kind of default layer type) -For a given layer, everything is quite straightforward. +For a given layer, everything is quite straightforward: - ``pre_dissect()`` is called to prepare the layer. - ``do_dissect()`` perform the real dissection of the layer. @@ -407,13 +407,13 @@ Sometimes, guessing the payload class is not as straightforward as defining a single port. For instance, it can depends on a value of a given byte in the current layer. The 2 needed methods are: - - ``guess_payload_class()`` which must return the guessed class for the - payload (next layer). By default, it uses links between classes - that have been put in place by ``bind_layers()``. +- ``guess_payload_class()`` which must return the guessed class for the + payload (next layer). By default, it uses links between classes + that have been put in place by ``bind_layers()``. - - ``default_payload_class()`` which returns the default value. This - method defined in the class ``Packet`` returns ``Raw``, but it can be - overloaded. +- ``default_payload_class()`` which returns the default value. This + method defined in the class ``Packet`` returns ``Raw``, but it can be + overloaded. For instance, decoding 802.11 changes depending on whether it is ciphered or not:: @@ -427,12 +427,12 @@ ciphered or not:: Several comments are needed here: - - this cannot be done using ``bind_layers()`` because the tests are - supposed to be "``field==value``", but it is more complicated here as we - test a single bit in the value of a field. +- this cannot be done using ``bind_layers()`` because the tests are + supposed to be "``field==value``", but it is more complicated here as we + test a single bit in the value of a field. - - if the test fails, no assumption is made, and we plug back to the - default guessing mechanisms calling ``Packet.guess_payload_class()`` +- if the test fails, no assumption is made, and we plug back to the + default guessing mechanisms calling ``Packet.guess_payload_class()`` Most of the time, defining a method ``guess_payload_class()`` is not a necessity as the same result can be obtained from ``bind_layers()``. @@ -468,8 +468,8 @@ default method ``Packet.guess_payload_class()``. This method runs through each element of the list payload_guess, each element being a tuple: - - the 1st value is a field to test (``'dport': 2000``) - - the 2nd value is the guessed class if it matches (``Skinny``) +- the 1st value is a field to test (``'dport': 2000``) +- the 2nd value is the guessed class if it matches (``Skinny``) So, the default ``guess_payload_class()`` tries all element in the list, until one matches. If no element are found, it then calls @@ -511,7 +511,7 @@ appended altogether. 0010 7F 00 00 01 00 14 00 50 00 00 00 00 00 00 00 00 .......P........ 0020 50 02 20 00 91 7C 00 00 P. ..|.. -Calling str() builds the packet: +Calling ``str()` builds the packet: - non instanced fields are set to their default value - lengths are updated automatically - checksums are computed @@ -906,6 +906,7 @@ e.g.:: Strings ------- +:: StrField(name, default, fmt="H", remain=0, shift=0) StrLenField(name, default, fld=None, length_from=None, shift=0): From 7c74489f0da3199f33aace22c12accb7b64bd335 Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Wed, 15 Apr 2009 19:08:01 +0200 Subject: [PATCH 40/92] Added graphics for automata --- doc/scapy/graphics/ATMT_HelloWorld.png | Bin 0 -> 7222 bytes doc/scapy/graphics/ATMT_TFTP_read.png | Bin 0 -> 31887 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/scapy/graphics/ATMT_HelloWorld.png create mode 100644 doc/scapy/graphics/ATMT_TFTP_read.png diff --git a/doc/scapy/graphics/ATMT_HelloWorld.png b/doc/scapy/graphics/ATMT_HelloWorld.png new file mode 100644 index 0000000000000000000000000000000000000000..e5b5ddb188a64bba6f71d07ff850d4f9601a0ce5 GIT binary patch literal 7222 zcmbuEcQl*v+y7(lO3_fNN{b+LS+%Jdd(WVDYlNcqtX5HUShZ_ZZ82+vRHar?s`efw zXzd;GB=N`ZobT^E-+!LhCGMOgC!cY@ulIG`cVhH()EH>lX#oHLgNC}Y0si;`|40Q= z;{UrEDR1Hrly;hG%I8FA%nqmUuh4j^n|tGr>cod?x2qfgz;RhaS<%RU;_q~TmG1aC z)s`u@2Dq1+_cX;pl$XA^Tkc^J<$VK(M8s`-SGzm*$Md~x%Dmk6{H|b!c<$3zM)Y@G z^r09WPV(?a7Ei;n;o)xa;qG=?=j7DCv*3QD@9g1}_rL>FO<)92>FLLE9};TrwL~Tg zZe8J!{(7cxs6m0&4SG<{zaUo}$AbrcFmnhUt8QFj4~p zJr4YEAYNLfR}d&m6wo{gOwC;l)(0v%=;z&t>dI4Aq<*Us6A9#rBh2eRKS(PI)-s-H zb@b}bw4&ut`PX~f9DB09Auk`ChGyp9IS9G$RAq(?JXrSItoC08#%*sy*g0yB&JH2$ z>`t>HqDkm728c1cucq0L4>z9f>DZgsDLKxjWP2# z3%ZI`^5zb4*zByun8*g?rVXv!)Bq`K1D8o+&K> zKP@aoFZ3iscG9UUyMcKE!#w&%pHV#U+8>y1W>7#pk8_^O@anJ0m5x#QpcIvC7s;7d z$ETZ+9r5T43A*~QatlNk{Ou|z%7s1NU#4dgsHJ7J8(?h>Nx1rLp#lc^REX$ePJMeh z4*OXBQ9uxWXcd2Z5$-h|VI~zH(Ksx$-@e=evQtER{W{ld_H6pnl$`U@?5_&WxYGj! z8(WthGH`r!b2bMpU}#j6gB@|+aR3F_@hJbLxmPjTW__+h9hTdbcSrv+9(T?skQiQxf%*Skb^jqxLnVfk78vY^<-)Ncr3)qbW--c=>m?@o<^`&Rm+8BAN)c07f%I&D%XTbm|O4 zPz}pMpJF2i5!<8Tt;dCsLOLbUED+b;M<2zconN{Z7V>B0T6O+|_9Ya#D`sbJvh#-u zd6bd=w^aI%Mg8wN2i9kKuM!6mwy336hY}#ICKB#CS^;Z*Rz8<=x-nkkJdjOdO+>+g zK*3N@X<(&ITj<$|*w)tkw`fK#XzxQM0^#rZb+csx3~=5lBCsYOjKSRRQqxLnq(NY% zC>3w4ot+y2`Xm9vPg)=Dp_=_RXOKZul0;ar-li2+jZc@MbC_R6x;b(bPkxsemzoF{ z9-8d86A5qhUg^&|%^6j(`r|#%bKVPm2IX(D0*+8Z* zuX@I|e-pSZw-C}1gi)SG1|9yJZjM+Y;E;=-Pq{@MX)Td(?F@1EiCUV~;bOxgovZ@+ zqx;VNnGd`vk5+QRaK|fLxoU~mWdoPy(X#kyOZ)x1R^`9FfR1A&yKU1Ne7wW^v)+9Y z{|6Oguzo*|ue6PDz0DijYZ}8G`3$uHth_26g~`)Tu^q{wl(@^rn!gC0u*(bcs5*k^rMoiBYjZ zAry7Az1W$^#W?lFYw>$LheEQTG5!r6&Q0PfMd$X2=Yvw*gYW^>$LB}5lT!j@;zSGr zUeaH^=gPSdF6X+exweq zQeV25>qwaZHNQ@#q}M{!jVfkq;26yf>J3_c_t~mP-(E6s(2_RqXb%1)pa&d_$v)hv zVSWfyC7hH!7e%vbhwgX0n&jLqF#qPQXOVh%p$6`+(X`_=i9hE;)DQ%M0n-<>*FeH09r><(UwLPICjMbXhsT*>PZ&DYuOwKvVjHr zZW}Aw#ux(4;uV1jgppVE6&e-I((mLgywD53c#@(x~+kAHolA(ft|h~Nb) zvfZL*qNIuZc_A;__A>Jtr@nMv>dQnTtMDtv=cwjs198X1!DprwQib(9zX@qC8$?PM z70xnzyGl$O%d>`nfH>&m0<_uzxp|6=xqLC&En zI$8vNoSibz69Bjq$-T(zzM$jYR6;5QglEkSU6I_{MGcV2Q#mnYiTRA^h4WHAmX;@y zOPsS9*(ba!gq-*k|EaM<$Kr(!SiackuZfk-M2x=8kHyfr?TjrUy2+?7(cwQ$B zutkNFkWtbDKKbixXD-kR%IJei!3DK6K6D(G$Brlqq^dqe#Sa8-#))} zNrB)@J{Zy}p(LN)ukfo2{phSXX{xbt0vg6ove}Mea+4(BGc5tTe1{$1N_SdH`nOAh zA2E}6K7n$Htk4K7*CY)X$n_#KiAGqS?&GRIw)y@WGg;z;h5@rA1lazqQN>i(nU!Jk zg!0#tR|<~y2R3?%Fabr`@JpOhKC3@Tsu~DTnf*2?iP$oEmaTV`yqp1VJ^qscFA!pF zQ|j(s4-7yvv%@*&#CQ@Kl9yVBR`#O|u4FtC|K|3Rr0wir65e*k#~Dpjnighec7#Cv zYJ#b?@pa1pw`IhTq2P>3`}X%GGOA1X?aV#uH64b!=gqMn+MQ!4Q@KhwAh3D=ytxLJ z=_cRxk#dfo7kRG{Z$h@dX)k-Vy&1@(=~kB>V8M6Ctq<+-!%5OErK6_;;3HSdR4Oq%g5EEQiMt(A?&1%lqz1jWvmB*xs`2 z-y-0NL5RUcMFhb5pJ{L{XGH6kH)#?b`bFw1kT~Se(NFmLjd8N7&R&BshMC(@4f;#L zg83MHTkwui<|7pa0RdS0g5es+eH!ka|F2z8dbo8AA{1!H9+n%#+H8g~xFRb@yE~Xq zsA%s}E>`!=pi|<(gB3CX!IqnB=d!Z?TXSEZQ$e}V?fJ?dxsQVBg0*NkD2jcGV`nbh z=V1N@WClJ@Fq}&3zJN}y;@A0iVdWAVd3u`}}7wiuD*Cec}< zb}RuLBM?|bfa%kjhj09@uE6@jj^?AeLXQ;J6gqNFb%_P2c&LRw0KKw22m5|ZgCi8& z+tvWLu~r|R?XS;*W#!o75=q@Jie2GAd?EoVqP%)Rn4o|G{TavXQJD*0ygf&dKRyRY z#IISc-9YnSVwZllF@~@&9IR+@mFAj4zs%B<)i%X91y6np&Q-SIPSz{i!U?SeeAZg3 zwM8f}RE?}uxsy@9l)*#vW4Z28 z-dv}He@-(_tQV)y*{vopCZNGCpb(fg- z*-3W-SbZwJtyfz5HFm?c_9f*QRjgMwhK-E!^{6ZJc1rf~^2E0iVB_v_EuBXAZHZ0t z^XDu4Y`QGi6nVS`=nE8gjSm|9S!UJ~K7lFN*aOzdBB7hb!^_(Cy> z5TY@+b1<(Luct@=ApTnWxVMpuAs9+vWxt{l?5do!gIV(!j z-)}(>Hg;_t*V96kMlGu)r@;O#%4Ru>Z>{u_b?j5uWzcnG!xA>j46Z>8W)_qK> z4FKZ`_X@J0yE~*UE#%ye198^{f?{!}W6Af=ClR55A%|)en4BEO!}_M{u+Yh(>C_lx zCKAOI{BIpm1c?N)6A(O;&u~%0Ai*u~(oTmh9#u-Va8V=%S7;hH;(OTT-qcu`p8@Z9D(3Wy7W4nx9kkZj<;E8*F&82y#s??R4}UZa)9VM`rq(x!(PgnJI`uA>o*pMvFB_hg-$iT#_G-Cz|8_tuy0JB8{_o8V-VoezI4Eck<=8^DMS0G zXxZ@A=0UO$zd}uoSAqJcp)7-&b|Y$SWOiKVAva83aAwm(vi@+Gk=`9FLfYj;Of=1I zelDkrl9dU&z439awR6j5+3}@Xgh_s1dM!H)+nES7)@#oGu({dVc8U|Zo}RRUT2OTg#9lM>5nhDB5S4Je%ap2C#DfF8r*H zWaJoovMy{kwd1w7Pq_o{r1k z%}f%&oQ6`yRKFXl=we2%(^Q67>jJLDPw`2!lN34s)0L8C23U0@3<_7gDizm1u>}iF zJXZ;`2e@Q*q|S3hmc=&1ldEbD#xS{zYUzhcBz(n%+D2tk3RRoc;T zjNx%IwaQ9^p-d@}jA4!d8P6|H+b9yiR0kq;zh4kpla4X>1D%CZg!haJHJh|viZYf) zNSCqyY|_y@AMs5Rxha65#opp}RTv2`BSB>@@s4=EiwNxZJ+D}ym+}00=s;sP;#kke z`>Oq}RQrOcD?q*4Wii8nB-q2P_1N3q)&*P8^Kda@&mA*4)F1#eF)&zeR^2XATsoF# z08A!Tejon?=>8z^vHJsHu0FF|-*8*4yzpWBRgj;R?e-=MKvL@8a9{pnctxpHhA*tp z=cB$R=XBnsY0Z} zNC49~=h5oP^KcQ@6F*EfrTSD3Tgn$3Of{1>zh(a1rZxE*#8LA^y4s~m1=IdMQ3Huy zP~-%`sYp@zf9_6R7o*aGk8;`uT4L@oX}?btrWgsA&ap0KUX=2orpgupPF{S&_e)%kpFN%f#|J^ye~<2Z2^m40_>?VVgq)gDff!%K z@W|~f&U(v+qidr!dT%~)I>%0=K=@*5ob%JthWLd-3aItP7)ta#r50?fQy3jg=y6AO z_qVs_KYBr$e4X^yi*&;>4+a(vTYt1pSwRwS9Pl@a=u|oXE#7;}qiigCM}NyQI@}}2 zx-v#GxcTqVMsGY?WNf{zQ)Y#gIB=AU+$)#jRwdO~y zptBD9pHZ{-qCjO<%PCc9ita209fK%keUodUFw=h{4^`0vX0l1r zHlkWzU}ZHUmk!os>siqCC7DauMrQfFj&SnQ&P*SKzgc3Q4#eN`UcRY6b!>s9s#|l7 zqGC-JArW}sz^tO4@r(1}1OVlMa9-H7YyQ6O2W8F1n)Yym8r&VdG{XSY!{g;wK0j=@ zR^aKtj5alu%EqnHzWJAq4_f-o!!y=zrzEJJ7jE(1vxu}SC42ozBJHA}%0yeQZeGCZ zgYIgj0S$$B@OL)vZr=eT;Ul>|g}qQwScLO@>)=6sXMBBldCIrj^%ki0xZBOGt<->j z3uZAEVPK>;?=PXdcj3;=Q+=U01}8H9n*$S>ugNzKeElCc=wj}@d-n+y|KSg-;ae>5LrAwaC=u6?1Yh_?nYpZxd54dSKJ&~wIG1OVG zUX=KeTroDK#|E$``A=hFQ8HKaMJQ{N5#Zm*7YDxvO2C(FXID|ll06;QmqiO4>JmDX z;ZJm-;BG*?%APXeDLKYG_ z_QQAQABprwN4!Bc*D4*5hI@0>t7>kWP=qAmy?gyD7)16OfGP`-X016uA7^F(ZdG-@ShOt zMAEk3VWIqq)ZLD_@s~&YvoR0#lQ7RJ$zk(6Ytf$vJ=&1rBEKCsDlvvz;y=k6XzIoe zU43j`-@?lgsmJccdx>9KvHa~HzTWtuFSS&wqA=defLyz;o$uK?+OIKDdn-hZyhxyb zmV-hv$XEr@9?Y0;de!UhP|{vLa13z+esqgH|5M}EIG&;%!Bx2|av(l(`6GU!Kpzz| kAD_oQZjJyY6*UbVVL25YW$Ffs8T@+y4gB*8CELjV0h3J$!vFvP literal 0 HcmV?d00001 diff --git a/doc/scapy/graphics/ATMT_TFTP_read.png b/doc/scapy/graphics/ATMT_TFTP_read.png new file mode 100644 index 0000000000000000000000000000000000000000..50621d976ac1addc852537ac49343ffd0e9e7209 GIT binary patch literal 31887 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 z Date: Wed, 15 Apr 2009 19:08:55 +0200 Subject: [PATCH 41/92] Fixed another unpaired quotation mark --- doc/scapy/advanced_usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/scapy/advanced_usage.rst b/doc/scapy/advanced_usage.rst index 0ae219996..ae9129827 100644 --- a/doc/scapy/advanced_usage.rst +++ b/doc/scapy/advanced_usage.rst @@ -530,7 +530,7 @@ In this example, we can see 3 decorators: have initial, final and error optional arguments set to non-zero for special states. * ``ATMT.condition`` that indicate a method to be run when the automaton state reaches the indicated state. The argument is the name of the method representing that state -* ``ATMT.action` binds a method to a transition and is run when the transition is taken. +* ``ATMT.action`` binds a method to a transition and is run when the transition is taken. Running this example gives the following result:: From 346c6606c03ce4ef249006d6fab4e73ae6afc388 Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Wed, 15 Apr 2009 19:35:28 +0200 Subject: [PATCH 42/92] Added README with build instructions for the docs --- doc/scapy/README | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 doc/scapy/README diff --git a/doc/scapy/README b/doc/scapy/README new file mode 100644 index 000000000..b34335927 --- /dev/null +++ b/doc/scapy/README @@ -0,0 +1,19 @@ +This folder includes source files (text and graphics) for Scapy's documentation, +which is automatically built using Sphinx + +The *.rst files are written as reStructuredText and should be quite readable +in your favourite text editor without any further formatting. + +To generate much nicer, searchable HTML docs, install Sphinx, open a command +line, change to the directory where this README is placed, and type the +following command: + + $ make html + +To generate a single PDF file (useful for printing) you need a working +LaTeX installation (e.g. ). +The following commands produce the file Scapy.pdf (>100 pages): + + $ make latex + $ cd _build/latex + $ make all-pdf \ No newline at end of file From e7a1f054f898db8c85c878b2eeb288eead81bb66 Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Thu, 16 Apr 2009 08:11:24 +0200 Subject: [PATCH 43/92] Fixed two more unpaired quotation marks --- doc/scapy/build_dissect.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/scapy/build_dissect.rst b/doc/scapy/build_dissect.rst index f621e7cac..70bdac75f 100644 --- a/doc/scapy/build_dissect.rst +++ b/doc/scapy/build_dissect.rst @@ -26,7 +26,7 @@ of a field class:: In this example, our layer has three fields. The first one is an 2 byte integer field named ``mickey`` and whose default value is 5. The second one is a 1 byte integer field named ``minnie`` and whose default value is 3. The difference between -a vanilla ``ByteField`` and a ``XByteField` is only the fact that the prefered human +a vanilla ``ByteField`` and a ``XByteField`` is only the fact that the prefered human representation of the field’s value is in hexadecimal. The last field is a 4 byte integer field named ``donald``. It is different from a vanilla ``IntField`` by the fact that some of the possible values of the field have litterate representations. For @@ -511,7 +511,7 @@ appended altogether. 0010 7F 00 00 01 00 14 00 50 00 00 00 00 00 00 00 00 .......P........ 0020 50 02 20 00 91 7C 00 00 P. ..|.. -Calling ``str()` builds the packet: +Calling ``str()`` builds the packet: - non instanced fields are set to their default value - lengths are updated automatically - checksums are computed From 964fd91c70301f110323d8d2bf8133e37c5acf05 Mon Sep 17 00:00:00 2001 From: Dirk Loss Date: Fri, 17 Apr 2009 21:07:28 +0200 Subject: [PATCH 44/92] Removed empty advanced_usage.rst file in top folder --- advanced_usage.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 advanced_usage.rst diff --git a/advanced_usage.rst b/advanced_usage.rst deleted file mode 100644 index e69de29bb..000000000 From a5863597062ce68521c2b9da9d5d6a4bac3ee15b Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 28 Apr 2009 18:40:23 +0200 Subject: [PATCH 45/92] Added Packet.firstlayer() --- scapy/packet.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scapy/packet.py b/scapy/packet.py index b96f3d17a..50bd770e6 100644 --- a/scapy/packet.py +++ b/scapy/packet.py @@ -716,6 +716,12 @@ Creates an EPS file describing a packet. If filename is not provided a temporary nb = track[0] return self.payload.getlayer(cls,nb,_track=_track) + def firstlayer(self): + q = self + while q.underlayer is not None: + q = q.underlayer + return q + def __getitem__(self, cls): if type(cls) is slice: lname = cls.start From 03f2cf9b6e54118a6ad0c03e86addc1714594c2a Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 28 Apr 2009 18:46:17 +0200 Subject: [PATCH 46/92] Added ability to reach a layer by his position Ex: pkt[4] or pkt.getlayer(4) --- scapy/packet.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scapy/packet.py b/scapy/packet.py index 50bd770e6..ca2f76410 100644 --- a/scapy/packet.py +++ b/scapy/packet.py @@ -689,11 +689,14 @@ Creates an EPS file describing a packet. If filename is not provided a temporary return self.payload.haslayer(cls) def getlayer(self, cls, nb=1, _track=None): """Return the nb^th layer that is an instance of cls.""" + if type(cls) is int: + nb = cls+1 + cls = None if type(cls) is str and "." in cls: ccls,fld = cls.split(".",1) else: ccls,fld = cls,None - if self.__class__ == cls or self.__class__.name == ccls: + if cls is None or self.__class__ == cls or self.__class__.name == ccls: if nb == 1: if fld is None: return self From 75d06db4c1e83c17ecd78f9b5ae0cfef0974cb11 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 28 Apr 2009 18:46:19 +0200 Subject: [PATCH 47/92] Added Packet.fragment() method --- scapy/packet.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scapy/packet.py b/scapy/packet.py index ca2f76410..5ee093f35 100644 --- a/scapy/packet.py +++ b/scapy/packet.py @@ -757,6 +757,9 @@ Creates an EPS file describing a packet. If filename is not provided a temporary def route(self): return (None,None,None) + + def fragment(self, *args, **kargs): + return self.payload.fragment(*args, **kargs) def display(self,*args,**kargs): # Deprecated. Use show() @@ -1034,6 +1037,8 @@ class NoPayload(Packet): if _track is not None: _track.append(nb) return None + def fragment(self, *args, **kargs): + raise Scapy_Exception("cannot fragment this packet") def show(self, indent=3, lvl="", label_lvl=""): pass def sprintf(self, fmt, relax): From efc373f5108efb351fad803ae817b64083f94728 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 28 Apr 2009 18:47:51 +0200 Subject: [PATCH 48/92] Added IP.fragment() method --- scapy/layers/inet.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/scapy/layers/inet.py b/scapy/layers/inet.py index 15897eb2d..ee3a163bd 100644 --- a/scapy/layers/inet.py +++ b/scapy/layers/inet.py @@ -387,7 +387,35 @@ class IP(Packet, IPTools): s += " frag:%i" % self.frag return s - + def fragment(self, fragsize=1480): + """Fragment IP datagrams""" + fragsize = (fragsize+7)/8*8 + lst = [] + fnb = 0 + fl = self + while fl.underlayer is not None: + fnb += 1 + fl = fl.underlayer + + for p in fl: + s = str(p[fnb].payload) + nb = (len(s)+fragsize-1)/fragsize + for i in range(nb): + q = p.copy() + del(q[fnb].payload) + del(q[fnb].chksum) + del(q[fnb].len) + if i == nb-1: + q[IP].flags &= ~1 + else: + q[IP].flags |= 1 + q[IP].frag = i*fragsize/8 + r = Raw(load=s[i*fragsize:(i+1)*fragsize]) + r.overload_fields = p[IP].payload.overload_fields.copy() + q.add_payload(r) + lst.append(q) + return lst + class TCP(Packet): name = "TCP" From de9371a22629232ee8b285a4f3befb75feb9bbc7 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 28 Apr 2009 19:32:59 +0200 Subject: [PATCH 49/92] Used Packet.fragment() method instead of fragment() function in Supersocket --- scapy/arch/linux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scapy/arch/linux.py b/scapy/arch/linux.py index 68089bd00..41514766d 100644 --- a/scapy/arch/linux.py +++ b/scapy/arch/linux.py @@ -382,7 +382,7 @@ class L3PacketSocket(SuperSocket): except socket.error,msg: x.sent_time = time.time() # bad approximation if conf.auto_fragment and msg[0] == 90: - for p in fragment(x): + for p in x.fragment(): self.outs.sendto(str(ll(p)), sdto) else: raise From 137306452039ab20d72ecce9f9be95bf29dcc597 Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 29 Apr 2009 11:24:09 +0200 Subject: [PATCH 50/92] WARNING: API CHANGE. Renamed RandDraw*() volatiles by RandEnum*() Sorry, I was not happy with the name introduced some days ago. --- scapy/volatile.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/scapy/volatile.py b/scapy/volatile.py index 7f4d78fd4..d1f72c7bb 100644 --- a/scapy/volatile.py +++ b/scapy/volatile.py @@ -12,7 +12,7 @@ from utils import corrupt_bits,corrupt_bytes #################### -class RandomSequence: +class RandomEnumeration: """iterate through a sequence in random order. When all the values have been drawn, if forever=1, the drawing is done again. If renewkeys=0, the draw will be in the same order, guaranteeing that the same @@ -105,10 +105,10 @@ class RandNumExpo(RandField): def _fix(self): return self.base+int(round(random.expovariate(self.lambd))) -class RandDraw(RandNum): +class RandEnum(RandNum): """Instances evaluate to integer sampling without replacement from the given interval""" def __init__(self, min, max): - self.seq = RandomSequence(min,max) + self.seq = RandomEnumeration(min,max) def _fix(self): return self.seq.next() @@ -144,37 +144,37 @@ class RandSLong(RandNum): def __init__(self): RandNum.__init__(self, -2L**63, 2L**63-1) -class RandDrawByte(RandDraw): +class RandEnumByte(RandEnum): def __init__(self): - RandDraw.__init__(self, 0, 2L**8-1) + RandEnum.__init__(self, 0, 2L**8-1) -class RandDrawSByte(RandDraw): +class RandEnumSByte(RandEnum): def __init__(self): - RandDraw.__init__(self, -2L**7, 2L**7-1) + RandEnum.__init__(self, -2L**7, 2L**7-1) -class RandDrawShort(RandDraw): +class RandEnumShort(RandEnum): def __init__(self): - RandDraw.__init__(self, 0, 2L**16-1) + RandEnum.__init__(self, 0, 2L**16-1) -class RandDrawSShort(RandDraw): +class RandEnumSShort(RandEnum): def __init__(self): - RandDraw.__init__(self, -2L**15, 2L**15-1) + RandEnum.__init__(self, -2L**15, 2L**15-1) -class RandDrawInt(RandDraw): +class RandEnumInt(RandEnum): def __init__(self): - RandDraw.__init__(self, 0, 2L**32-1) + RandEnum.__init__(self, 0, 2L**32-1) -class RandDrawSInt(RandDraw): +class RandEnumSInt(RandEnum): def __init__(self): - RandDraw.__init__(self, -2L**31, 2L**31-1) + RandEnum.__init__(self, -2L**31, 2L**31-1) -class RandDrawLong(RandDraw): +class RandEnumLong(RandEnum): def __init__(self): - RandDraw.__init__(self, 0, 2L**64-1) + RandEnum.__init__(self, 0, 2L**64-1) -class RandDrawSLong(RandDraw): +class RandEnumSLong(RandEnum): def __init__(self): - RandDraw.__init__(self, -2L**63, 2L**63-1) + RandEnum.__init__(self, -2L**63, 2L**63-1) class RandChoice(RandField): def __init__(self, *args): From e776adae94bad3e71f7db7bc32ecf1492417344a Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 30 Apr 2009 12:38:15 +0200 Subject: [PATCH 51/92] Fixed typo in doc --- doc/scapy/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/scapy/usage.rst b/doc/scapy/usage.rst index a1b788bf1..80704f7fd 100644 --- a/doc/scapy/usage.rst +++ b/doc/scapy/usage.rst @@ -797,7 +797,7 @@ Using the ``export_object()`` function, Scapy can export a base64 encoded Python WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6 ... -The output above can be reimported back into Skype using ``import_object()``:: +The output above can be reimported back into Scapy using ``import_object()``:: >>> new_pkt = import_object() eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST From b2bb8e4e7ee65f096701dcdb28bac412c3df6a14 Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 30 Apr 2009 16:05:44 +0200 Subject: [PATCH 52/92] Preliminary work on automata --- scapy/automaton.py | 141 +++++++++++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 57 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index b5be2b143..95f638fe5 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -204,6 +204,7 @@ class Automaton: __metaclass__ = Automaton_metaclass def __init__(self, *args, **kargs): + self.running = False self.debug_level=0 self.init_args=args self.init_kargs=kargs @@ -247,7 +248,10 @@ class Automaton: self.debug(2, "%s [%s] not taken" % (cond.atmt_type, cond.atmt_condname)) - def run(self, *args, **kargs): + + def start(self, *args, **kargs): + self.running = True + # Update default parameters a = args+self.init_args[len(args):] k = self.init_kargs @@ -257,66 +261,91 @@ class Automaton: # Start the automaton self.state=self.initial_states[0](self) self.send_sock = conf.L3socket() - l = conf.L2listen(**self.socket_kargs) + self.listen_sock = conf.L2listen(**self.socket_kargs) self.packets = PacketList(name="session[%s]"%self.__class__.__name__) + + def next(self): + if not self.running: + self.start() + try: + self.debug(1, "## state=[%s]" % self.state.state) + + # Entering a new state. First, call new state function + state_output = self.state.run() + if self.state.error: + self.running = False + raise self.ErrorState("Reached %s: [%r]" % (self.state.state, state_output), result=state_output) + if self.state.final: + self.running = False + raise StopIteration(state_output) + + if state_output is None: + state_output = () + elif type(state_output) is not list: + state_output = state_output, + + # Then check immediate conditions + for cond in self.conditions[self.state.state]: + self.run_condition(cond, *state_output) + + # If still there and no conditions left, we are stuck! + if ( len(self.recv_conditions[self.state.state]) == 0 + and len(self.timeout[self.state.state]) == 1 ): + raise self.Stuck("stuck in [%s]" % self.state.state,result=state_output) + + # Finally listen and pay attention to timeouts + expirations = iter(self.timeout[self.state.state]) + next_timeout,timeout_func = expirations.next() + t0 = time.time() + + while 1: + t = time.time()-t0 + if next_timeout is not None: + if next_timeout <= t: + self.run_condition(timeout_func, *state_output) + next_timeout,timeout_func = expirations.next() + if next_timeout is None: + remain = None + else: + remain = next_timeout-t + + r,_,_ = select([self.listen_sock],[],[],remain) + if self.listen_sock in r: + pkt = self.listen_sock.recv(MTU) + if pkt is not None: + if self.master_filter(pkt): + self.debug(3, "RECVD: %s" % pkt.summary()) + for rcvcond in self.recv_conditions[self.state.state]: + self.run_condition(rcvcond, pkt, *state_output) + else: + self.debug(4, "FILTR: %s" % pkt.summary()) + + except ATMT.NewStateRequested,state_req: + self.debug(2, "switching from [%s] to [%s]" % (self.state.state,state_req.state)) + self.state = state_req + return state_req + + + + def run(self, *args, **kargs): + if not self.running: + self.start(*args, **kargs) + while 1: try: - self.debug(1, "## state=[%s]" % self.state.state) - - # Entering a new state. First, call new state function - state_output = self.state.run() - if self.state.error: - raise self.ErrorState("Reached %s: [%r]" % (self.state.state, state_output), result=state_output) - if self.state.final: - return state_output - - if state_output is None: - state_output = () - elif type(state_output) is not list: - state_output = state_output, - - # Then check immediate conditions - for cond in self.conditions[self.state.state]: - self.run_condition(cond, *state_output) - - # If still there and no conditions left, we are stuck! - if ( len(self.recv_conditions[self.state.state]) == 0 - and len(self.timeout[self.state.state]) == 1 ): - raise self.Stuck("stuck in [%s]" % self.state.state,result=state_output) - - # Finally listen and pay attention to timeouts - expirations = iter(self.timeout[self.state.state]) - next_timeout,timeout_func = expirations.next() - t0 = time.time() - - while 1: - t = time.time()-t0 - if next_timeout is not None: - if next_timeout <= t: - self.run_condition(timeout_func, *state_output) - next_timeout,timeout_func = expirations.next() - if next_timeout is None: - remain = None - else: - remain = next_timeout-t - - r,_,_ = select([l],[],[],remain) - if l in r: - pkt = l.recv(MTU) - if pkt is not None: - if self.master_filter(pkt): - self.debug(3, "RECVD: %s" % pkt.summary()) - for rcvcond in self.recv_conditions[self.state.state]: - self.run_condition(rcvcond, pkt, *state_output) - else: - self.debug(4, "FILTR: %s" % pkt.summary()) - - except ATMT.NewStateRequested,state_req: - self.debug(2, "switching from [%s] to [%s]" % (self.state.state,state_req.state)) - self.state = state_req + self.next() except KeyboardInterrupt: self.debug(1,"Interrupted by user") break + except StopIteration,e: + return e.args[0] + + cont = run + + def __iter__(self): + if not self.running: + self.start() + return self def my_send(self, pkt): self.send_sock.send(pkt) @@ -326,5 +355,3 @@ class Automaton: self.debug(3,"SENT : %s" % pkt.summary()) self.packets.append(pkt.copy()) - - From a20fd5bc6564cb1a73557fc842259a583255860e Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 30 Apr 2009 16:05:50 +0200 Subject: [PATCH 53/92] Added automata breakpoint management --- scapy/automaton.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/scapy/automaton.py b/scapy/automaton.py index 95f638fe5..338a12764 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -205,6 +205,8 @@ class Automaton: def __init__(self, *args, **kargs): self.running = False + self.breakpointed = None + self.breakpoints = set() self.debug_level=0 self.init_args=args self.init_kargs=kargs @@ -224,6 +226,11 @@ class Automaton: class Stuck(ErrorState): pass + class Breakpoint(Exception): + def __init__(self, msg, breakpoint): + Exception.__init__(self, msg) + self.breakpoint = breakpoint + def parse_args(self, debug=0, store=1, **kargs): self.debug_level=debug self.socket_kargs = kargs @@ -248,6 +255,18 @@ class Automaton: self.debug(2, "%s [%s] not taken" % (cond.atmt_type, cond.atmt_condname)) + def add_breakpoints(self, *bps): + for bp in bps: + if hasattr(bp,"atmt_state"): + bp = bp.atmt_state + self.breakpoints.add(bp) + + def remove_breakpoints(self, *bps): + for bp in bps: + if hasattr(bp,"atmt_state"): + bp = bp.atmt_state + if bp in self.breakpoints: + self.breakpoints.remove(pb) def start(self, *args, **kargs): self.running = True @@ -271,6 +290,11 @@ class Automaton: self.debug(1, "## state=[%s]" % self.state.state) # Entering a new state. First, call new state function + if self.state.state in self.breakpoints and self.state.state != self.breakpointed: + self.breakpointed = self.state.state + raise self.Breakpoint("breakpoint triggered on state %s" % self.state.state, + breakpoint = self.state.state) + self.breakpointed = None state_output = self.state.run() if self.state.error: self.running = False From d8b8156b1e56d8b7538f04acf521a104ae62eb50 Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 30 Apr 2009 16:08:52 +0200 Subject: [PATCH 54/92] Added automata IOevent transition conditions --- scapy/automaton.py | 119 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 102 insertions(+), 17 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 338a12764..10839ef79 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -3,14 +3,30 @@ ## Copyright (C) Philippe Biondi ## This program is published under a GPLv2 license -import types,itertools,time +import types,itertools,time,os from select import select +from collections import deque +import thread from config import conf from utils import do_graph from error import log_interactive from plist import PacketList from data import MTU +class ObjectPipe: + def __init__(self): + self.rd,self.wr = os.pipe() + self.queue = deque() + def fileno(self): + return self.rd + def send(self, obj): + self.queue.append(obj) + os.write(self.wr,"X") + def recv(self, n=0): + os.read(self.rd,1) + return self.queue.popleft() + + ############## ## Automata ## ############## @@ -21,6 +37,7 @@ class ATMT: CONDITION = "Condition" RECV = "Receive condition" TIMEOUT = "Timeout condition" + IOEVENT = "I/O event" class NewStateRequested(Exception): def __init__(self, state_func, automaton, *args, **kargs): @@ -89,6 +106,16 @@ class ATMT: return f return deco @staticmethod + def ioevent(state, name, prio=0): + def deco(f, state=state): + f.atmt_type = ATMT.IOEVENT + f.atmt_state = state.atmt_state + f.atmt_condname = f.func_name + f.atmt_ioname = name + f.atmt_prio = prio + return f + return deco + @staticmethod def timeout(state, timeout): def deco(f, state=state, timeout=timeout): f.atmt_type = ATMT.TIMEOUT @@ -106,9 +133,11 @@ class Automaton_metaclass(type): cls.state = None cls.recv_conditions={} cls.conditions={} + cls.ioevents={} cls.timeout={} cls.actions={} cls.initial_states=[] + cls.ionames = [] members = {} classes = [cls] @@ -127,11 +156,12 @@ class Automaton_metaclass(type): s = m.atmt_state cls.states[s] = m cls.recv_conditions[s]=[] + cls.ioevents[s]=[] cls.conditions[s]=[] cls.timeout[s]=[] if m.atmt_initial: cls.initial_states.append(m) - elif m.atmt_type in [ATMT.CONDITION, ATMT.RECV, ATMT.TIMEOUT]: + elif m.atmt_type in [ATMT.CONDITION, ATMT.RECV, ATMT.TIMEOUT, ATMT.IOEVENT]: cls.actions[m.atmt_condname] = [] for m in decorated: @@ -139,6 +169,9 @@ class Automaton_metaclass(type): cls.conditions[m.atmt_state].append(m) elif m.atmt_type == ATMT.RECV: cls.recv_conditions[m.atmt_state].append(m) + elif m.atmt_type == ATMT.IOEVENT: + cls.ioevents[m.atmt_state].append(m) + cls.ionames.append(m.atmt_ioname) elif m.atmt_type == ATMT.TIMEOUT: cls.timeout[m.atmt_state].append((m.atmt_timeout, m)) elif m.atmt_type == ATMT.ACTION: @@ -150,7 +183,8 @@ class Automaton_metaclass(type): v.sort(lambda (t1,f1),(t2,f2): cmp(t1,t2)) v.append((None, None)) for v in itertools.chain(cls.conditions.itervalues(), - cls.recv_conditions.itervalues()): + cls.recv_conditions.itervalues(), + cls.ioevents.itervalues()): v.sort(lambda c1,c2: cmp(c1.atmt_prio,c2.atmt_prio)) for condname,actlst in cls.actions.iteritems(): actlst.sort(lambda c1,c2: cmp(c1.atmt_cond[condname], c2.atmt_cond[condname])) @@ -177,7 +211,9 @@ class Automaton_metaclass(type): s += '\t"%s" -> "%s" [ color=green ];\n' % (st.atmt_state,n) - for c,k,v in [("purple",k,v) for k,v in self.conditions.items()]+[("red",k,v) for k,v in self.recv_conditions.items()]: + for c,k,v in ([("purple",k,v) for k,v in self.conditions.items()]+ + [("red",k,v) for k,v in self.recv_conditions.items()]+ + [("orange",k,v) for k,v in self.ioevents.items()]): for f in v: for n in f.func_code.co_names+f.func_code.co_consts: if n in self.states: @@ -203,6 +239,28 @@ class Automaton_metaclass(type): class Automaton: __metaclass__ = Automaton_metaclass + class _IO: + pass + + class _IO_wrapper: + def __init__(self,rd,wr): + self.rd = rd + self.wr = wr + def fileno(self): + if type(self.rd) is int: + return self.rd + return self.rd.fileno() + def recv(self, n=None): + return self.rd.recv(n) + def read(self, n=None): + return self.rd.recv(n) + def send(self, msg): + return self.wr.send(msg) + def write(self, msg): + return self.wr.send(msg) + + + def __init__(self, *args, **kargs): self.running = False self.breakpointed = None @@ -210,6 +268,18 @@ class Automaton: self.debug_level=0 self.init_args=args self.init_kargs=kargs + self.io = self._IO() + self.oi = self._IO() + self.ioin = {} + self.ioout = {} + for n in self.ionames: + self.ioin[n] = ioin = ObjectPipe() + self.ioout[n] = ioout = ObjectPipe() + ioin.ioname = n + ioout.ioname = n + setattr(self.io, n, self._IO_wrapper(ioout,ioin)) + setattr(self.oi, n, self._IO_wrapper(ioin,ioout)) + self.parse_args(*args, **kargs) def debug(self, lvl, msg): @@ -313,8 +383,9 @@ class Automaton: self.run_condition(cond, *state_output) # If still there and no conditions left, we are stuck! - if ( len(self.recv_conditions[self.state.state]) == 0 - and len(self.timeout[self.state.state]) == 1 ): + if ( len(self.recv_conditions[self.state.state]) == 0 and + len(self.ioevents[self.state.state]) == 0 and + len(self.timeout[self.state.state]) == 1 ): raise self.Stuck("stuck in [%s]" % self.state.state,result=state_output) # Finally listen and pay attention to timeouts @@ -322,6 +393,11 @@ class Automaton: next_timeout,timeout_func = expirations.next() t0 = time.time() + fds = [] + if len(self.recv_conditions[self.state.state]) > 0: + fds.append(self.listen_sock) + for ioev in self.ioevents[self.state.state]: + fds.append(self.ioin[ioev.atmt_ioname]) while 1: t = time.time()-t0 if next_timeout is not None: @@ -333,16 +409,22 @@ class Automaton: else: remain = next_timeout-t - r,_,_ = select([self.listen_sock],[],[],remain) - if self.listen_sock in r: - pkt = self.listen_sock.recv(MTU) - if pkt is not None: - if self.master_filter(pkt): - self.debug(3, "RECVD: %s" % pkt.summary()) - for rcvcond in self.recv_conditions[self.state.state]: - self.run_condition(rcvcond, pkt, *state_output) - else: - self.debug(4, "FILTR: %s" % pkt.summary()) + r,_,_ = select(fds,[],[],remain) + for fd in r: + if fd == self.listen_sock: + pkt = self.listen_sock.recv(MTU) + if pkt is not None: + if self.master_filter(pkt): + self.debug(3, "RECVD: %s" % pkt.summary()) + for rcvcond in self.recv_conditions[self.state.state]: + self.run_condition(rcvcond, pkt, *state_output) + else: + self.debug(4, "FILTR: %s" % pkt.summary()) + else: + self.debug(3, "IOEVENT on %s" % fd.ioname) + for ioevt in self.ioevents[self.state.state]: + if ioevt.atmt_ioname == fd.ioname: + self.run_condition(ioevt, fd, *state_output) except ATMT.NewStateRequested,state_req: self.debug(2, "switching from [%s] to [%s]" % (self.state.state,state_req.state)) @@ -365,7 +447,10 @@ class Automaton: return e.args[0] cont = run - + + def run_bg(self, *args, **kargs): + self.threadid = thread.start_new_thread(self.run, args, kargs) + def __iter__(self): if not self.running: self.start() From 952833b12ce10081f1fc4866199070bc0c78ba27 Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 30 Apr 2009 16:08:54 +0200 Subject: [PATCH 55/92] Added automaton "debugging" capabilities --- scapy/automaton.py | 159 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 133 insertions(+), 26 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 10839ef79..f35dc0d51 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -27,6 +27,15 @@ class ObjectPipe: return self.queue.popleft() +class Message: + def __init__(self, **args): + self.__dict__.update(args) + def __repr__(self): + return "" % " ".join("%s=%r"%(k,v) + for (k,v) in self.__dict__.iteritems() + if not k.startswith("_")) + + ############## ## Automata ## ############## @@ -57,6 +66,8 @@ class ATMT: return self def run(self): return self.func(self.automaton, *self.args, **self.kargs) + def __repr__(self): + return "NewStateRequested(%s)" % self.state @staticmethod def state(initial=0,final=0,error=0): @@ -125,6 +136,16 @@ class ATMT: return f return deco +class _ATMT_Command: + RUN = "RUN" + NEXT = "NEXT" + STOP = "STOP" + END = "END" + SINGLESTEP = "SINGLESTEP" + INTERCEPT = "INTERCEPT" + ACCEPT = "ACCEPT" + REPLACE = "REPLACE" + REJECT = "REJECT" class Automaton_metaclass(type): def __new__(cls, name, bases, dct): @@ -239,9 +260,6 @@ class Automaton_metaclass(type): class Automaton: __metaclass__ = Automaton_metaclass - class _IO: - pass - class _IO_wrapper: def __init__(self,rd,wr): self.rd = rd @@ -258,18 +276,20 @@ class Automaton: return self.wr.send(msg) def write(self, msg): return self.wr.send(msg) - - def __init__(self, *args, **kargs): self.running = False + self.threadid = None self.breakpointed = None self.breakpoints = set() + self.interception_points = set() self.debug_level=0 self.init_args=args self.init_kargs=kargs - self.io = self._IO() - self.oi = self._IO() + self.io = type.__new__(type, "IOnamespace",(),{}) + self.oi = type.__new__(type, "IOnamespace",(),{}) + self.cmdin = ObjectPipe() + self.cmdout = ObjectPipe() self.ioin = {} self.ioout = {} for n in self.ionames: @@ -301,6 +321,9 @@ class Automaton: Exception.__init__(self, msg) self.breakpoint = breakpoint + class CommandMessage(Exception): + pass + def parse_args(self, debug=0, store=1, **kargs): self.debug_level=debug self.socket_kargs = kargs @@ -325,6 +348,19 @@ class Automaton: self.debug(2, "%s [%s] not taken" % (cond.atmt_type, cond.atmt_condname)) + def add_interception_points(self, *ipts): + for ipt in ipts: + if hasattr(ipt,"atmt_state"): + ipt = ipt.atmt_state + self.interception_points.add(ipt) + + def remove_interception_points(self, *ipts): + for ipt in ipts: + if hasattr(ipt,"atmt_state"): + ipt = ipt.atmt_state + if ipt in self.interception_points: + self.interception_points.remove(ipt) + def add_breakpoints(self, *bps): for bp in bps: if hasattr(bp,"atmt_state"): @@ -353,12 +389,48 @@ class Automaton: self.listen_sock = conf.L2listen(**self.socket_kargs) self.packets = PacketList(name="session[%s]"%self.__class__.__name__) - def next(self): - if not self.running: - self.start() + self.threadid = thread.start_new_thread(self.do_control, ()) + + def do_control(self): + singlestep = True + self.debug(3, "Starting control thread [tid=%i]" % self.threadid) + stop = False + while not stop: + c = self.cmdin.recv() + self.debug(5, "Received command %s" % c.type) + if c.type == _ATMT_Command.RUN: + singlestep = False + elif c.type == _ATMT_Command.NEXT: + singlestep = True + elif c.type == _ATMT_Command.STOP: + break + while True: + try: + state = self.do_next() + except KeyboardInterrupt: + self.debug(1,"Interrupted by user") + stop=True + break + except self.CommandMessage: + break + except StopIteration,e: + c = Message(type=_ATMT_Command.END, result=e.args[0]) + self.cmdout.send(c) + stop=True + break + if singlestep: + c = Message(type=_ATMT_Command.SINGLESTEP,state=state) + self.cmdout.send(c) + break + self.debug(3, "Stopping control thread (tid=%i)"%self.threadid) + self.threadid = None + + + def do_next(self): try: self.debug(1, "## state=[%s]" % self.state.state) + # Entering a new state. First, call new state function if self.state.state in self.breakpoints and self.state.state != self.breakpointed: self.breakpointed = self.state.state @@ -393,7 +465,7 @@ class Automaton: next_timeout,timeout_func = expirations.next() t0 = time.time() - fds = [] + fds = [self.cmdin] if len(self.recv_conditions[self.state.state]) > 0: fds.append(self.listen_sock) for ioev in self.ioevents[self.state.state]: @@ -409,8 +481,11 @@ class Automaton: else: remain = next_timeout-t + self.debug(5, "Select on %r" % fds) r,_,_ = select(fds,[],[],remain) for fd in r: + if fd == self.cmdin: + raise self.CommandMessage() if fd == self.listen_sock: pkt = self.listen_sock.recv(MTU) if pkt is not None: @@ -431,26 +506,43 @@ class Automaton: self.state = state_req return state_req - - - def run(self, *args, **kargs): - if not self.running: - self.start(*args, **kargs) - - while 1: + def run(self, resume=None, wait=True): + if resume is None: + resume = Message(type = _ATMT_Command.RUN) + self.cmdin.send(resume) + if wait: try: - self.next() + c = self.cmdout.recv() except KeyboardInterrupt: - self.debug(1,"Interrupted by user") - break - except StopIteration,e: - return e.args[0] + return + if c.type == _ATMT_Command.END: + return c.result + elif c.type == _ATMT_Command.INTERCEPT: + print "Packet intercepted" + return c.pkt + elif c.type == _ATMT_Command.SINGLESTEP: + return c.state - cont = run + def next(self): + return self.run(resume = Message(type=_ATMT_Command.NEXT)) - def run_bg(self, *args, **kargs): - self.threadid = thread.start_new_thread(self.run, args, kargs) + def stop(self): + self.cmdin.send(Message(type=_ATMT_Command.STOP)) + + def accept_packet(self, pkt=None): + rsm = Message() + if pkt is None: + rsm.type = _ATMT_Command.ACCEPT + else: + rsm.type = _ATMT_Command.REPLACE + rsm.pkt = pkt + return self.run(resume=rsm) + def reject_packet(self): + rsm = Message(type = _ATMT_Command.REJECT) + return self.run(resume=rsm) + + def __iter__(self): if not self.running: self.start() @@ -460,6 +552,21 @@ class Automaton: self.send_sock.send(pkt) def send(self, pkt): + if self.state.state in self.interception_points: + self.debug(3,"INTERCEPT: packet intercepted: %s" % pkt.summary()) + cmd = Message(type = _ATMT_Command.INTERCEPT, pkt = pkt) + self.cmdout.send(cmd) + cmd = self.cmdin.recv() + if cmd.type == _ATMT_Command.REJECT: + self.debug(3,"INTERCEPT: packet rejected") + return + elif cmd.type == _ATMT_Command.REPLACE: + self.debug(3,"INTERCEPT: packet replaced") + pkt = cmd.pkt + elif cmd.type == _ATMT_Command.ACCEPT: + self.debug(3,"INTERCEPT: packet accepted") + else: + self.debug(1,"INTERCEPT: unkown verdict: %r" % cmd.type) self.my_send(pkt) self.debug(3,"SENT : %s" % pkt.summary()) self.packets.append(pkt.copy()) From 36f92713891aeecaa1c67cbac164080f8e09a027 Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 30 Apr 2009 16:11:15 +0200 Subject: [PATCH 56/92] Automaton thread's exception tracebacks transfered to foreground --- scapy/automaton.py | 60 ++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index f35dc0d51..3ccc52c04 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -141,6 +141,7 @@ class _ATMT_Command: NEXT = "NEXT" STOP = "STOP" END = "END" + EXCEPTION = "EXCEPTION" SINGLESTEP = "SINGLESTEP" INTERCEPT = "INTERCEPT" ACCEPT = "ACCEPT" @@ -395,33 +396,38 @@ class Automaton: singlestep = True self.debug(3, "Starting control thread [tid=%i]" % self.threadid) stop = False - while not stop: - c = self.cmdin.recv() - self.debug(5, "Received command %s" % c.type) - if c.type == _ATMT_Command.RUN: - singlestep = False - elif c.type == _ATMT_Command.NEXT: - singlestep = True - elif c.type == _ATMT_Command.STOP: - break - while True: - try: - state = self.do_next() - except KeyboardInterrupt: - self.debug(1,"Interrupted by user") - stop=True - break - except self.CommandMessage: - break - except StopIteration,e: - c = Message(type=_ATMT_Command.END, result=e.args[0]) - self.cmdout.send(c) - stop=True - break - if singlestep: - c = Message(type=_ATMT_Command.SINGLESTEP,state=state) - self.cmdout.send(c) + try: + while not stop: + c = self.cmdin.recv() + self.debug(5, "Received command %s" % c.type) + if c.type == _ATMT_Command.RUN: + singlestep = False + elif c.type == _ATMT_Command.NEXT: + singlestep = True + elif c.type == _ATMT_Command.STOP: break + while True: + try: + state = self.do_next() + except KeyboardInterrupt: + self.debug(1,"Interrupted by user") + stop=True + break + except self.CommandMessage: + break + except StopIteration,e: + c = Message(type=_ATMT_Command.END, result=e.args[0]) + self.cmdout.send(c) + stop=True + break + if singlestep: + c = Message(type=_ATMT_Command.SINGLESTEP,state=state) + self.cmdout.send(c) + break + except Exception,e: + self.debug(3, "Transfering exception [%s] from tid=%i"% (e,self.threadid)) + m = Message(type = _ATMT_Command.EXCEPTION, exception=e) + self.cmdout.send(m) self.debug(3, "Stopping control thread (tid=%i)"%self.threadid) self.threadid = None @@ -522,6 +528,8 @@ class Automaton: return c.pkt elif c.type == _ATMT_Command.SINGLESTEP: return c.state + elif c.type == _ATMT_Command.EXCEPTION: + raise c.exception def next(self): return self.run(resume = Message(type=_ATMT_Command.NEXT)) From 974dbc4b88fe7aced15a3f20c34dd07046535914 Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 30 Apr 2009 16:11:54 +0200 Subject: [PATCH 57/92] Simplify automaton control loop --- scapy/automaton.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 3ccc52c04..f268f8091 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -395,9 +395,8 @@ class Automaton: def do_control(self): singlestep = True self.debug(3, "Starting control thread [tid=%i]" % self.threadid) - stop = False try: - while not stop: + while True: c = self.cmdin.recv() self.debug(5, "Received command %s" % c.type) if c.type == _ATMT_Command.RUN: @@ -409,21 +408,15 @@ class Automaton: while True: try: state = self.do_next() - except KeyboardInterrupt: - self.debug(1,"Interrupted by user") - stop=True - break except self.CommandMessage: break - except StopIteration,e: - c = Message(type=_ATMT_Command.END, result=e.args[0]) - self.cmdout.send(c) - stop=True - break if singlestep: c = Message(type=_ATMT_Command.SINGLESTEP,state=state) self.cmdout.send(c) break + except StopIteration,e: + c = Message(type=_ATMT_Command.END, result=e.args[0]) + self.cmdout.send(c) except Exception,e: self.debug(3, "Transfering exception [%s] from tid=%i"% (e,self.threadid)) m = Message(type = _ATMT_Command.EXCEPTION, exception=e) From 975dcc5704de5cd26ce8c8c1ab5cddccecf72b24 Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 30 Apr 2009 16:12:41 +0200 Subject: [PATCH 58/92] Reorganized Automaton methods --- scapy/automaton.py | 173 +++++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 85 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index f268f8091..b9a41c1bf 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -261,6 +261,20 @@ class Automaton_metaclass(type): class Automaton: __metaclass__ = Automaton_metaclass + ## Methods to overload + def parse_args(self, debug=0, store=1, **kargs): + self.debug_level=debug + self.socket_kargs = kargs + self.store_packets = store + + def master_filter(self, pkt): + return True + + def my_send(self, pkt): + self.send_sock.send(pkt) + + + ## Utility classes and exceptions class _IO_wrapper: def __init__(self,rd,wr): self.rd = rd @@ -277,7 +291,30 @@ class Automaton: return self.wr.send(msg) def write(self, msg): return self.wr.send(msg) - + + class ErrorState(Exception): + def __init__(self, msg, result=None): + Exception.__init__(self, msg) + self.result = result + class Stuck(ErrorState): + pass + + class Breakpoint(Exception): + def __init__(self, msg, breakpoint): + Exception.__init__(self, msg) + self.breakpoint = breakpoint + + class CommandMessage(Exception): + pass + + + ## Services + def debug(self, lvl, msg): + if self.debug_level >= lvl: + log_interactive.debug(msg) + + + ## Internals def __init__(self, *args, **kargs): self.running = False self.threadid = None @@ -303,37 +340,6 @@ class Automaton: self.parse_args(*args, **kargs) - def debug(self, lvl, msg): - if self.debug_level >= lvl: - log_interactive.debug(msg) - - - - - class ErrorState(Exception): - def __init__(self, msg, result=None): - Exception.__init__(self, msg) - self.result = result - class Stuck(ErrorState): - pass - - class Breakpoint(Exception): - def __init__(self, msg, breakpoint): - Exception.__init__(self, msg) - self.breakpoint = breakpoint - - class CommandMessage(Exception): - pass - - def parse_args(self, debug=0, store=1, **kargs): - self.debug_level=debug - self.socket_kargs = kargs - self.store_packets = store - - - def master_filter(self, pkt): - return True - def run_condition(self, cond, *args, **kargs): try: cond(self,*args, **kargs) @@ -347,50 +353,11 @@ class Automaton: raise else: self.debug(2, "%s [%s] not taken" % (cond.atmt_type, cond.atmt_condname)) - - def add_interception_points(self, *ipts): - for ipt in ipts: - if hasattr(ipt,"atmt_state"): - ipt = ipt.atmt_state - self.interception_points.add(ipt) - - def remove_interception_points(self, *ipts): - for ipt in ipts: - if hasattr(ipt,"atmt_state"): - ipt = ipt.atmt_state - if ipt in self.interception_points: - self.interception_points.remove(ipt) - - def add_breakpoints(self, *bps): - for bp in bps: - if hasattr(bp,"atmt_state"): - bp = bp.atmt_state - self.breakpoints.add(bp) - - def remove_breakpoints(self, *bps): - for bp in bps: - if hasattr(bp,"atmt_state"): - bp = bp.atmt_state - if bp in self.breakpoints: - self.breakpoints.remove(pb) - - def start(self, *args, **kargs): - self.running = True - - # Update default parameters - a = args+self.init_args[len(args):] - k = self.init_kargs - k.update(kargs) - self.parse_args(*a,**k) - - # Start the automaton - self.state=self.initial_states[0](self) - self.send_sock = conf.L3socket() - self.listen_sock = conf.L2listen(**self.socket_kargs) - self.packets = PacketList(name="session[%s]"%self.__class__.__name__) - - self.threadid = thread.start_new_thread(self.do_control, ()) + def __iter__(self): + if not self.running: + self.start() + return self def do_control(self): singlestep = True @@ -429,7 +396,6 @@ class Automaton: try: self.debug(1, "## state=[%s]" % self.state.state) - # Entering a new state. First, call new state function if self.state.state in self.breakpoints and self.state.state != self.breakpointed: self.breakpointed = self.state.state @@ -504,6 +470,52 @@ class Automaton: self.debug(2, "switching from [%s] to [%s]" % (self.state.state,state_req.state)) self.state = state_req return state_req + + + ## Public API + def add_interception_points(self, *ipts): + for ipt in ipts: + if hasattr(ipt,"atmt_state"): + ipt = ipt.atmt_state + self.interception_points.add(ipt) + + def remove_interception_points(self, *ipts): + for ipt in ipts: + if hasattr(ipt,"atmt_state"): + ipt = ipt.atmt_state + if ipt in self.interception_points: + self.interception_points.remove(ipt) + + def add_breakpoints(self, *bps): + for bp in bps: + if hasattr(bp,"atmt_state"): + bp = bp.atmt_state + self.breakpoints.add(bp) + + def remove_breakpoints(self, *bps): + for bp in bps: + if hasattr(bp,"atmt_state"): + bp = bp.atmt_state + if bp in self.breakpoints: + self.breakpoints.remove(pb) + + def start(self, *args, **kargs): + self.running = True + + # Update default parameters + a = args+self.init_args[len(args):] + k = self.init_kargs + k.update(kargs) + self.parse_args(*a,**k) + + # Start the automaton + self.state=self.initial_states[0](self) + self.send_sock = conf.L3socket() + self.listen_sock = conf.L2listen(**self.socket_kargs) + self.packets = PacketList(name="session[%s]"%self.__class__.__name__) + + self.threadid = thread.start_new_thread(self.do_control, ()) + def run(self, resume=None, wait=True): if resume is None: @@ -529,7 +541,6 @@ class Automaton: def stop(self): self.cmdin.send(Message(type=_ATMT_Command.STOP)) - def accept_packet(self, pkt=None): rsm = Message() @@ -544,14 +555,6 @@ class Automaton: return self.run(resume=rsm) - def __iter__(self): - if not self.running: - self.start() - return self - - def my_send(self, pkt): - self.send_sock.send(pkt) - def send(self, pkt): if self.state.state in self.interception_points: self.debug(3,"INTERCEPT: packet intercepted: %s" % pkt.summary()) From a49638ead6cbe6ef7829fa3030ed083df9b3136a Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 30 Apr 2009 16:13:09 +0200 Subject: [PATCH 59/92] Small redesign of automaton control loop and logic --- scapy/automaton.py | 104 +++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index b9a41c1bf..b5b1cb012 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -3,6 +3,7 @@ ## Copyright (C) Philippe Biondi ## This program is published under a GPLv2 license +from __future__ import with_statement import types,itertools,time,os from select import select from collections import deque @@ -316,7 +317,7 @@ class Automaton: ## Internals def __init__(self, *args, **kargs): - self.running = False + self.running = thread.allocate_lock() self.threadid = None self.breakpointed = None self.breakpoints = set() @@ -340,6 +341,8 @@ class Automaton: self.parse_args(*args, **kargs) + self.start() + def run_condition(self, cond, *args, **kargs): try: cond(self,*args, **kargs) @@ -355,41 +358,54 @@ class Automaton: self.debug(2, "%s [%s] not taken" % (cond.atmt_type, cond.atmt_condname)) def __iter__(self): - if not self.running: - self.start() return self - def do_control(self): - singlestep = True - self.debug(3, "Starting control thread [tid=%i]" % self.threadid) - try: - while True: - c = self.cmdin.recv() - self.debug(5, "Received command %s" % c.type) - if c.type == _ATMT_Command.RUN: - singlestep = False - elif c.type == _ATMT_Command.NEXT: - singlestep = True - elif c.type == _ATMT_Command.STOP: - break + def do_control(self, *args, **kargs): + with self.running: + self.threadid = thread.get_ident() + + # Update default parameters + a = args+self.init_args[len(args):] + k = self.init_kargs + k.update(kargs) + self.parse_args(*a,**k) + + # Start the automaton + self.state=self.initial_states[0](self) + self.send_sock = conf.L3socket() + self.listen_sock = conf.L2listen(**self.socket_kargs) + self.packets = PacketList(name="session[%s]"%self.__class__.__name__) + + singlestep = True + self.debug(3, "Starting control thread [tid=%i]" % self.threadid) + try: while True: - try: - state = self.do_next() - except self.CommandMessage: + c = self.cmdin.recv() + self.debug(5, "Received command %s" % c.type) + if c.type == _ATMT_Command.RUN: + singlestep = False + elif c.type == _ATMT_Command.NEXT: + singlestep = True + elif c.type == _ATMT_Command.STOP: break - if singlestep: - c = Message(type=_ATMT_Command.SINGLESTEP,state=state) - self.cmdout.send(c) - break - except StopIteration,e: - c = Message(type=_ATMT_Command.END, result=e.args[0]) - self.cmdout.send(c) - except Exception,e: - self.debug(3, "Transfering exception [%s] from tid=%i"% (e,self.threadid)) - m = Message(type = _ATMT_Command.EXCEPTION, exception=e) - self.cmdout.send(m) - self.debug(3, "Stopping control thread (tid=%i)"%self.threadid) - self.threadid = None + while True: + try: + state = self.do_next() + except self.CommandMessage: + break + if singlestep: + c = Message(type=_ATMT_Command.SINGLESTEP,state=state) + self.cmdout.send(c) + break + except StopIteration,e: + c = Message(type=_ATMT_Command.END, result=e.args[0]) + self.cmdout.send(c) + except Exception,e: + self.debug(3, "Transfering exception [%s] from tid=%i"% (e,self.threadid)) + m = Message(type = _ATMT_Command.EXCEPTION, exception=e) + self.cmdout.send(m) + self.debug(3, "Stopping control thread (tid=%i)"%self.threadid) + self.threadid = None def do_next(self): @@ -404,10 +420,8 @@ class Automaton: self.breakpointed = None state_output = self.state.run() if self.state.error: - self.running = False raise self.ErrorState("Reached %s: [%r]" % (self.state.state, state_output), result=state_output) if self.state.final: - self.running = False raise StopIteration(state_output) if state_output is None: @@ -470,6 +484,7 @@ class Automaton: self.debug(2, "switching from [%s] to [%s]" % (self.state.state,state_req.state)) self.state = state_req return state_req + ## Public API @@ -500,22 +515,7 @@ class Automaton: self.breakpoints.remove(pb) def start(self, *args, **kargs): - self.running = True - - # Update default parameters - a = args+self.init_args[len(args):] - k = self.init_kargs - k.update(kargs) - self.parse_args(*a,**k) - - # Start the automaton - self.state=self.initial_states[0](self) - self.send_sock = conf.L3socket() - self.listen_sock = conf.L2listen(**self.socket_kargs) - self.packets = PacketList(name="session[%s]"%self.__class__.__name__) - - self.threadid = thread.start_new_thread(self.do_control, ()) - + thread.start_new_thread(self.do_control, args, kargs) def run(self, resume=None, wait=True): if resume is None: @@ -542,6 +542,10 @@ class Automaton: def stop(self): self.cmdin.send(Message(type=_ATMT_Command.STOP)) + def restart(self): + self.stop() + self.start() + def accept_packet(self, pkt=None): rsm = Message() if pkt is None: From d8d496bace74a59ca6842a319ad925458f7eea3a Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 30 Apr 2009 16:13:33 +0200 Subject: [PATCH 60/92] Added new regression tests for automata --- test/regression.uts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/regression.uts b/test/regression.uts index f53dc4404..3d36ca81f 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -895,6 +895,41 @@ class ATMT6(Automaton): a=ATMT6() a.run() assert( _ == 'Mercury' ) + += Automaton test io event +~ automaton + +class ATMT7(Automaton): + @ATMT.state(initial=1) + def BEGIN(self): + self.res = "S" + @ATMT.ioevent(BEGIN, name="tst") + def tr1(self, fd): + self.res += fd.recv() + raise self.NEXT_STATE() + @ATMT.state() + def NEXT_STATE(self): + self.oi.tst.send("ur") + @ATMT.ioevent(NEXT_STATE, name="tst") + def tr2(self, fd): + self.res += fd.recv() + raise self.END() + @ATMT.state(final=1) + def END(self): + self.res += "n" + return self.res + +a=ATMT7() +a.run(wait=False) +a.io.tst.send("at") +a.io.tst.recv() +a.io.tst.send(_) +a.run() +assert( _ == "Saturn" ) + += Automaton test io event from external fd +~ automaton +pass + Test IP options From bfae396ab3b373b7d081a75a1b139907f4ea0a4b Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 30 Apr 2009 16:14:28 +0200 Subject: [PATCH 61/92] Added ability to provide external file descriptors for IOevents in automata --- scapy/automaton.py | 44 ++++++++++++++++++++++++++++++++++++++------ test/regression.uts | 29 ++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index b5b1cb012..3c500131c 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -276,7 +276,26 @@ class Automaton: ## Utility classes and exceptions - class _IO_wrapper: + class _IO_fdwrapper: + def __init__(self,rd,wr): + if rd is not None and type(rd) is not int: + rd = rd.fileno() + if wr is not None and type(wr) is not int: + wr = wr.fileno() + self.rd = rd + self.wr = wr + def fileno(self): + return self.rd + def read(self, n): + return os.read(self.rd, n) + def write(self, msg): + return os.write(self.wr,msg) + def recv(self, n): + return self.read(n) + def send(self, msg): + return self.write(msg) + + class _IO_mixer: def __init__(self,rd,wr): self.rd = rd self.wr = wr @@ -316,7 +335,7 @@ class Automaton: ## Internals - def __init__(self, *args, **kargs): + def __init__(self, external_fd={}, *args, **kargs): self.running = thread.allocate_lock() self.threadid = None self.breakpointed = None @@ -332,12 +351,25 @@ class Automaton: self.ioin = {} self.ioout = {} for n in self.ionames: - self.ioin[n] = ioin = ObjectPipe() - self.ioout[n] = ioout = ObjectPipe() + extfd = external_fd.get(n) + if type(extfd) is not tuple: + extfd = (extfd,None) + ioin,ioout = extfd + if ioin is None: + ioin = ObjectPipe() + elif type(ioin) is int: + ioin = self._IO_fdwrapper(ioin,None) + if ioout is None: + ioout = ObjectPipe() + elif type(ioout) is int: + ioin = self._IO_fdwrapper(None,ioout) + + self.ioin[n] = ioin + self.ioout[n] = ioout ioin.ioname = n ioout.ioname = n - setattr(self.io, n, self._IO_wrapper(ioout,ioin)) - setattr(self.oi, n, self._IO_wrapper(ioin,ioout)) + setattr(self.io, n, self._IO_mixer(ioout,ioin)) + setattr(self.oi, n, self._IO_mixer(ioin,ioout)) self.parse_args(*args, **kargs) diff --git a/test/regression.uts b/test/regression.uts index 3d36ca81f..8c69930a1 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -929,7 +929,34 @@ assert( _ == "Saturn" ) = Automaton test io event from external fd ~ automaton -pass +class ATMT8(Automaton): + @ATMT.state(initial=1) + def BEGIN(self): + self.res = "U" + @ATMT.ioevent(BEGIN, name="extfd") + def tr1(self, fd): + self.res += fd.read(2) + raise self.NEXT_STATE() + @ATMT.state() + def NEXT_STATE(self): + pass + @ATMT.ioevent(NEXT_STATE, name="extfd") + def tr2(self, fd): + self.res += fd.read(2) + raise self.END() + @ATMT.state(final=1) + def END(self): + self.res += "s" + return self.res + +r,w = os.pipe() + +a=ATMT8(external_fd={"extfd":r}) +a.run(wait=False) +os.write(w,"ra") +os.write(w,"nu") +a.run() +assert( _ == "Uranus" ) + Test IP options From a5e447b6dc3605f2d6770e7296913fd8a3a82f9c Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 00:25:52 +0200 Subject: [PATCH 62/92] Wrapped Automaton state methods with objects to give them .breaks() and .intercepts() methods --- scapy/automaton.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/scapy/automaton.py b/scapy/automaton.py index 3c500131c..84b1ff5b4 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -36,6 +36,21 @@ class Message: for (k,v) in self.__dict__.iteritems() if not k.startswith("_")) +class instance_state: + def __init__(self, instance): + self.im_self = instance.im_self + self.im_func = instance.im_func + self.im_class = instance.im_class + def __getattr__(self, attr): + return getattr(self.im_func, attr) + + def __call__(self, *args, **kargs): + return self.im_func(self.im_self, *args, **kargs) + def breaks(self): + return self.im_self.add_breakpoints(self.im_func) + def intercepts(self): + return self.im_self.add_interception_points(self.im_func) + ############## ## Automata ## @@ -370,6 +385,12 @@ class Automaton: ioout.ioname = n setattr(self.io, n, self._IO_mixer(ioout,ioin)) setattr(self.oi, n, self._IO_mixer(ioin,ioout)) + + + for stname in self.states: + setattr(self, stname, + instance_state(getattr(self, stname))) + self.parse_args(*args, **kargs) From 135d5a3e85334128d11303750e5f056834e3d14c Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 00:25:52 +0200 Subject: [PATCH 63/92] Automaton: a bit of reorganization and made internal methods begin with underscore --- scapy/automaton.py | 75 +++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 84b1ff5b4..f7a644151 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -346,8 +346,28 @@ class Automaton: ## Services def debug(self, lvl, msg): if self.debug_level >= lvl: - log_interactive.debug(msg) - + log_interactive.debug(msg) + + def send(self, pkt): + if self.state.state in self.interception_points: + self.debug(3,"INTERCEPT: packet intercepted: %s" % pkt.summary()) + cmd = Message(type = _ATMT_Command.INTERCEPT, pkt = pkt) + self.cmdout.send(cmd) + cmd = self.cmdin.recv() + if cmd.type == _ATMT_Command.REJECT: + self.debug(3,"INTERCEPT: packet rejected") + return + elif cmd.type == _ATMT_Command.REPLACE: + self.debug(3,"INTERCEPT: packet replaced") + pkt = cmd.pkt + elif cmd.type == _ATMT_Command.ACCEPT: + self.debug(3,"INTERCEPT: packet accepted") + else: + self.debug(1,"INTERCEPT: unkown verdict: %r" % cmd.type) + self.my_send(pkt) + self.debug(3,"SENT : %s" % pkt.summary()) + self.packets.append(pkt.copy()) + ## Internals def __init__(self, external_fd={}, *args, **kargs): @@ -386,17 +406,18 @@ class Automaton: setattr(self.io, n, self._IO_mixer(ioout,ioin)) setattr(self.oi, n, self._IO_mixer(ioin,ioout)) - for stname in self.states: setattr(self, stname, instance_state(getattr(self, stname))) - self.parse_args(*args, **kargs) self.start() - def run_condition(self, cond, *args, **kargs): + def __iter__(self): + return self + + def _run_condition(self, cond, *args, **kargs): try: cond(self,*args, **kargs) except ATMT.NewStateRequested, state_req: @@ -410,10 +431,7 @@ class Automaton: else: self.debug(2, "%s [%s] not taken" % (cond.atmt_type, cond.atmt_condname)) - def __iter__(self): - return self - - def do_control(self, *args, **kargs): + def _do_control(self, *args, **kargs): with self.running: self.threadid = thread.get_ident() @@ -443,7 +461,7 @@ class Automaton: break while True: try: - state = self.do_next() + state = self._do_next() except self.CommandMessage: break if singlestep: @@ -460,8 +478,7 @@ class Automaton: self.debug(3, "Stopping control thread (tid=%i)"%self.threadid) self.threadid = None - - def do_next(self): + def _do_next(self): try: self.debug(1, "## state=[%s]" % self.state.state) @@ -484,7 +501,7 @@ class Automaton: # Then check immediate conditions for cond in self.conditions[self.state.state]: - self.run_condition(cond, *state_output) + self._run_condition(cond, *state_output) # If still there and no conditions left, we are stuck! if ( len(self.recv_conditions[self.state.state]) == 0 and @@ -506,7 +523,7 @@ class Automaton: t = time.time()-t0 if next_timeout is not None: if next_timeout <= t: - self.run_condition(timeout_func, *state_output) + self._run_condition(timeout_func, *state_output) next_timeout,timeout_func = expirations.next() if next_timeout is None: remain = None @@ -524,14 +541,14 @@ class Automaton: if self.master_filter(pkt): self.debug(3, "RECVD: %s" % pkt.summary()) for rcvcond in self.recv_conditions[self.state.state]: - self.run_condition(rcvcond, pkt, *state_output) + self._run_condition(rcvcond, pkt, *state_output) else: self.debug(4, "FILTR: %s" % pkt.summary()) else: self.debug(3, "IOEVENT on %s" % fd.ioname) for ioevt in self.ioevents[self.state.state]: if ioevt.atmt_ioname == fd.ioname: - self.run_condition(ioevt, fd, *state_output) + self._run_condition(ioevt, fd, *state_output) except ATMT.NewStateRequested,state_req: self.debug(2, "switching from [%s] to [%s]" % (self.state.state,state_req.state)) @@ -539,7 +556,6 @@ class Automaton: return state_req - ## Public API def add_interception_points(self, *ipts): for ipt in ipts: @@ -568,7 +584,7 @@ class Automaton: self.breakpoints.remove(pb) def start(self, *args, **kargs): - thread.start_new_thread(self.do_control, args, kargs) + thread.start_new_thread(self._do_control, args, kargs) def run(self, resume=None, wait=True): if resume is None: @@ -589,6 +605,9 @@ class Automaton: elif c.type == _ATMT_Command.EXCEPTION: raise c.exception + def runbg(self, resume=None, wait=False): + self.run(resume, wait) + def next(self): return self.run(resume = Message(type=_ATMT_Command.NEXT)) @@ -607,28 +626,10 @@ class Automaton: rsm.type = _ATMT_Command.REPLACE rsm.pkt = pkt return self.run(resume=rsm) + def reject_packet(self): rsm = Message(type = _ATMT_Command.REJECT) return self.run(resume=rsm) - def send(self, pkt): - if self.state.state in self.interception_points: - self.debug(3,"INTERCEPT: packet intercepted: %s" % pkt.summary()) - cmd = Message(type = _ATMT_Command.INTERCEPT, pkt = pkt) - self.cmdout.send(cmd) - cmd = self.cmdin.recv() - if cmd.type == _ATMT_Command.REJECT: - self.debug(3,"INTERCEPT: packet rejected") - return - elif cmd.type == _ATMT_Command.REPLACE: - self.debug(3,"INTERCEPT: packet replaced") - pkt = cmd.pkt - elif cmd.type == _ATMT_Command.ACCEPT: - self.debug(3,"INTERCEPT: packet accepted") - else: - self.debug(1,"INTERCEPT: unkown verdict: %r" % cmd.type) - self.my_send(pkt) - self.debug(3,"SENT : %s" % pkt.summary()) - self.packets.append(pkt.copy()) From 6aec08585cff1c1b682f2ad3b06c99df2ce3e055 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 00:25:52 +0200 Subject: [PATCH 64/92] Better Automaton exception hierarchy, and some cleanups --- scapy/automaton.py | 65 ++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index f7a644151..4b4b1fd8b 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -155,6 +155,7 @@ class ATMT: class _ATMT_Command: RUN = "RUN" NEXT = "NEXT" + FREEZE = "FREEZE" STOP = "STOP" END = "END" EXCEPTION = "EXCEPTION" @@ -327,19 +328,30 @@ class Automaton: def write(self, msg): return self.wr.send(msg) - class ErrorState(Exception): + + class AutomatonException(Exception): + pass + + class ErrorState(AutomatonException): def __init__(self, msg, result=None): Exception.__init__(self, msg) self.result = result class Stuck(ErrorState): pass - class Breakpoint(Exception): - def __init__(self, msg, breakpoint): + class AutomatonStopped(AutomatonException): + def __init__(self, msg, state=None): Exception.__init__(self, msg) - self.breakpoint = breakpoint + self.state = state + + class Breakpoint(AutomatonStopped): + pass + class InterceptionPoint(AutomatonStopped): + def __init__(self, msg, state, packet): + Automaton.AutomatonStopped.__init__(self, msg, state) + self.packet = packet - class CommandMessage(Exception): + class CommandMessage(AutomatonException): pass @@ -351,7 +363,7 @@ class Automaton: def send(self, pkt): if self.state.state in self.interception_points: self.debug(3,"INTERCEPT: packet intercepted: %s" % pkt.summary()) - cmd = Message(type = _ATMT_Command.INTERCEPT, pkt = pkt) + cmd = Message(type = _ATMT_Command.INTERCEPT, state=self.state, pkt=pkt) self.cmdout.send(cmd) cmd = self.cmdin.recv() if cmd.type == _ATMT_Command.REJECT: @@ -431,13 +443,18 @@ class Automaton: else: self.debug(2, "%s [%s] not taken" % (cond.atmt_type, cond.atmt_condname)) + def _do_start(self, *args, **kargs): + + thread.start_new_thread(self._do_control, args, kargs) + + def _do_control(self, *args, **kargs): with self.running: self.threadid = thread.get_ident() # Update default parameters a = args+self.init_args[len(args):] - k = self.init_kargs + k = self.init_kargs.copy() k.update(kargs) self.parse_args(*a,**k) @@ -457,6 +474,8 @@ class Automaton: singlestep = False elif c.type == _ATMT_Command.NEXT: singlestep = True + elif c.type == _ATMT_Command.FREEZE: + continue elif c.type == _ATMT_Command.STOP: break while True: @@ -486,7 +505,7 @@ class Automaton: if self.state.state in self.breakpoints and self.state.state != self.breakpointed: self.breakpointed = self.state.state raise self.Breakpoint("breakpoint triggered on state %s" % self.state.state, - breakpoint = self.state.state) + state = self.state.state) self.breakpointed = None state_output = self.state.run() if self.state.error: @@ -567,8 +586,7 @@ class Automaton: for ipt in ipts: if hasattr(ipt,"atmt_state"): ipt = ipt.atmt_state - if ipt in self.interception_points: - self.interception_points.remove(ipt) + self.interception_points.discard(ipt) def add_breakpoints(self, *bps): for bp in bps: @@ -580,11 +598,11 @@ class Automaton: for bp in bps: if hasattr(bp,"atmt_state"): bp = bp.atmt_state - if bp in self.breakpoints: - self.breakpoints.remove(pb) + self.breakpoints.discard(pb) def start(self, *args, **kargs): - thread.start_new_thread(self._do_control, args, kargs) + if not self.running.locked(): + self._do_start(*args, **kargs) def run(self, resume=None, wait=True): if resume is None: @@ -594,14 +612,14 @@ class Automaton: try: c = self.cmdout.recv() except KeyboardInterrupt: + self.cmdin.send(Message(type = _ATMT_Command.FREEZE)) return if c.type == _ATMT_Command.END: return c.result elif c.type == _ATMT_Command.INTERCEPT: - print "Packet intercepted" - return c.pkt + raise self.InterceptionPoint("packet intercepted", state=c.state.state, packet=c.pkt) elif c.type == _ATMT_Command.SINGLESTEP: - return c.state + raise self.Breakpoint("singlestep", state=c.state.state) elif c.type == _ATMT_Command.EXCEPTION: raise c.exception @@ -612,24 +630,25 @@ class Automaton: return self.run(resume = Message(type=_ATMT_Command.NEXT)) def stop(self): - self.cmdin.send(Message(type=_ATMT_Command.STOP)) + if self.running.locked(): + self.cmdin.send(Message(type=_ATMT_Command.STOP)) - def restart(self): + def restart(self, *args, **kargs): self.stop() - self.start() + self.start(*args, **kargs) - def accept_packet(self, pkt=None): + def accept_packet(self, pkt=None, wait=True): rsm = Message() if pkt is None: rsm.type = _ATMT_Command.ACCEPT else: rsm.type = _ATMT_Command.REPLACE rsm.pkt = pkt - return self.run(resume=rsm) + return self.run(resume=rsm, wait=wait) - def reject_packet(self): + def reject_packet(self, wait=True): rsm = Message(type = _ATMT_Command.REJECT) - return self.run(resume=rsm) + return self.run(resume=rsm, wait=wait) From a0e92141281d68c85912832a9af805e7ca4463bf Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 00:25:52 +0200 Subject: [PATCH 65/92] Added more debugging messages to automaton --- scapy/automaton.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 4b4b1fd8b..ce1a2b0e4 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -370,8 +370,8 @@ class Automaton: self.debug(3,"INTERCEPT: packet rejected") return elif cmd.type == _ATMT_Command.REPLACE: - self.debug(3,"INTERCEPT: packet replaced") pkt = cmd.pkt + self.debug(3,"INTERCEPT: packet replaced by: %s" % pkt.summary()) elif cmd.type == _ATMT_Command.ACCEPT: self.debug(3,"INTERCEPT: packet accepted") else: @@ -431,6 +431,7 @@ class Automaton: def _run_condition(self, cond, *args, **kargs): try: + self.debug(5, "Trying %s [%s]" % (cond.atmt_type, cond.atmt_condname)) cond(self,*args, **kargs) except ATMT.NewStateRequested, state_req: self.debug(2, "%s [%s] taken to state [%s]" % (cond.atmt_type, cond.atmt_condname, state_req.state)) @@ -440,6 +441,9 @@ class Automaton: self.debug(2, " + Running action [%s]" % action.func_name) action(self, *state_req.action_args, **state_req.action_kargs) raise + except Exception,e: + self.debug(2, "%s [%s] raised exception [%s]" % (cond.atmt_type, cond.atmt_condname, e)) + raise else: self.debug(2, "%s [%s] not taken" % (cond.atmt_type, cond.atmt_condname)) @@ -551,7 +555,9 @@ class Automaton: self.debug(5, "Select on %r" % fds) r,_,_ = select(fds,[],[],remain) + self.debug(5, "Selected %r" % r) for fd in r: + self.debug(5, "Looking at %r" % fd) if fd == self.cmdin: raise self.CommandMessage() if fd == self.listen_sock: From 7cf152490e6d9205769931d862451e89236b0151 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 00:25:52 +0200 Subject: [PATCH 66/92] Rewrote Automaton._do_next() as a coroutine to fix bugs with command interactions --- scapy/automaton.py | 175 +++++++++++++++++++++++---------------------- 1 file changed, 91 insertions(+), 84 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index ce1a2b0e4..7d0aae48a 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -469,6 +469,7 @@ class Automaton: self.packets = PacketList(name="session[%s]"%self.__class__.__name__) singlestep = True + iterator = self._do_iter() self.debug(3, "Starting control thread [tid=%i]" % self.threadid) try: while True: @@ -483,9 +484,8 @@ class Automaton: elif c.type == _ATMT_Command.STOP: break while True: - try: - state = self._do_next() - except self.CommandMessage: + state = iterator.next() + if isinstance(state, self.CommandMessage): break if singlestep: c = Message(type=_ATMT_Command.SINGLESTEP,state=state) @@ -501,85 +501,85 @@ class Automaton: self.debug(3, "Stopping control thread (tid=%i)"%self.threadid) self.threadid = None - def _do_next(self): - try: - self.debug(1, "## state=[%s]" % self.state.state) - - # Entering a new state. First, call new state function - if self.state.state in self.breakpoints and self.state.state != self.breakpointed: - self.breakpointed = self.state.state - raise self.Breakpoint("breakpoint triggered on state %s" % self.state.state, - state = self.state.state) - self.breakpointed = None - state_output = self.state.run() - if self.state.error: - raise self.ErrorState("Reached %s: [%r]" % (self.state.state, state_output), result=state_output) - if self.state.final: - raise StopIteration(state_output) - - if state_output is None: - state_output = () - elif type(state_output) is not list: - state_output = state_output, - - # Then check immediate conditions - for cond in self.conditions[self.state.state]: - self._run_condition(cond, *state_output) - - # If still there and no conditions left, we are stuck! - if ( len(self.recv_conditions[self.state.state]) == 0 and - len(self.ioevents[self.state.state]) == 0 and - len(self.timeout[self.state.state]) == 1 ): - raise self.Stuck("stuck in [%s]" % self.state.state,result=state_output) - - # Finally listen and pay attention to timeouts - expirations = iter(self.timeout[self.state.state]) - next_timeout,timeout_func = expirations.next() - t0 = time.time() - - fds = [self.cmdin] - if len(self.recv_conditions[self.state.state]) > 0: - fds.append(self.listen_sock) - for ioev in self.ioevents[self.state.state]: - fds.append(self.ioin[ioev.atmt_ioname]) - while 1: - t = time.time()-t0 - if next_timeout is not None: - if next_timeout <= t: - self._run_condition(timeout_func, *state_output) - next_timeout,timeout_func = expirations.next() - if next_timeout is None: - remain = None - else: - remain = next_timeout-t - - self.debug(5, "Select on %r" % fds) - r,_,_ = select(fds,[],[],remain) - self.debug(5, "Selected %r" % r) - for fd in r: - self.debug(5, "Looking at %r" % fd) - if fd == self.cmdin: - raise self.CommandMessage() - if fd == self.listen_sock: - pkt = self.listen_sock.recv(MTU) - if pkt is not None: - if self.master_filter(pkt): - self.debug(3, "RECVD: %s" % pkt.summary()) - for rcvcond in self.recv_conditions[self.state.state]: - self._run_condition(rcvcond, pkt, *state_output) - else: - self.debug(4, "FILTR: %s" % pkt.summary()) + def _do_iter(self): + while True: + try: + self.debug(1, "## state=[%s]" % self.state.state) + + # Entering a new state. First, call new state function + if self.state.state in self.breakpoints and self.state.state != self.breakpointed: + self.breakpointed = self.state.state + raise self.Breakpoint("breakpoint triggered on state %s" % self.state.state, + state = self.state.state) + self.breakpointed = None + state_output = self.state.run() + if self.state.error: + raise self.ErrorState("Reached %s: [%r]" % (self.state.state, state_output), result=state_output) + if self.state.final: + raise StopIteration(state_output) + + if state_output is None: + state_output = () + elif type(state_output) is not list: + state_output = state_output, + + # Then check immediate conditions + for cond in self.conditions[self.state.state]: + self._run_condition(cond, *state_output) + + # If still there and no conditions left, we are stuck! + if ( len(self.recv_conditions[self.state.state]) == 0 and + len(self.ioevents[self.state.state]) == 0 and + len(self.timeout[self.state.state]) == 1 ): + raise self.Stuck("stuck in [%s]" % self.state.state,result=state_output) + + # Finally listen and pay attention to timeouts + expirations = iter(self.timeout[self.state.state]) + next_timeout,timeout_func = expirations.next() + t0 = time.time() + + fds = [self.cmdin] + if len(self.recv_conditions[self.state.state]) > 0: + fds.append(self.listen_sock) + for ioev in self.ioevents[self.state.state]: + fds.append(self.ioin[ioev.atmt_ioname]) + while 1: + t = time.time()-t0 + if next_timeout is not None: + if next_timeout <= t: + self._run_condition(timeout_func, *state_output) + next_timeout,timeout_func = expirations.next() + if next_timeout is None: + remain = None else: - self.debug(3, "IOEVENT on %s" % fd.ioname) - for ioevt in self.ioevents[self.state.state]: - if ioevt.atmt_ioname == fd.ioname: - self._run_condition(ioevt, fd, *state_output) - - except ATMT.NewStateRequested,state_req: - self.debug(2, "switching from [%s] to [%s]" % (self.state.state,state_req.state)) - self.state = state_req - return state_req - + remain = next_timeout-t + + self.debug(5, "Select on %r" % fds) + r,_,_ = select(fds,[],[],remain) + self.debug(5, "Selected %r" % r) + for fd in r: + self.debug(5, "Looking at %r" % fd) + if fd == self.cmdin: + yield self.CommandMessage() + elif fd == self.listen_sock: + pkt = self.listen_sock.recv(MTU) + if pkt is not None: + if self.master_filter(pkt): + self.debug(3, "RECVD: %s" % pkt.summary()) + for rcvcond in self.recv_conditions[self.state.state]: + self._run_condition(rcvcond, pkt, *state_output) + else: + self.debug(4, "FILTR: %s" % pkt.summary()) + else: + self.debug(3, "IOEVENT on %s" % fd.ioname) + for ioevt in self.ioevents[self.state.state]: + if ioevt.atmt_ioname == fd.ioname: + self._run_condition(ioevt, fd, *state_output) + + except ATMT.NewStateRequested,state_req: + self.debug(2, "switching from [%s] to [%s]" % (self.state.state,state_req.state)) + self.state = state_req + yield state_req ## Public API def add_interception_points(self, *ipts): @@ -636,9 +636,16 @@ class Automaton: return self.run(resume = Message(type=_ATMT_Command.NEXT)) def stop(self): - if self.running.locked(): - self.cmdin.send(Message(type=_ATMT_Command.STOP)) - + self.cmdin.send(Message(type=_ATMT_Command.STOP)) + with self.running: + # Flush command pipes + while True: + r,_,_ = select([self.cmdin, self.cmdout],[],[],0) + if not r: + break + for fd in r: + fd.recv() + def restart(self, *args, **kargs): self.stop() self.start(*args, **kargs) From c3c3b5a87d8b800ea011ec2238eafe9d1150d99f Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 00:25:52 +0200 Subject: [PATCH 67/92] Added new automaton regression tests --- test/regression.uts | 61 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/test/regression.uts b/test/regression.uts index 8c69930a1..f5b5f732c 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -896,6 +896,10 @@ a=ATMT6() a.run() assert( _ == 'Mercury' ) +a.restart() +a.run() +assert( _ == 'Mercury' ) + = Automaton test io event ~ automaton @@ -927,6 +931,14 @@ a.io.tst.send(_) a.run() assert( _ == "Saturn" ) +a.restart() +a.run(wait=False) +a.io.tst.send("at") +a.io.tst.recv() +a.io.tst.send(_) +a.run() +assert( _ == "Saturn" ) + = Automaton test io event from external fd ~ automaton class ATMT8(Automaton): @@ -957,7 +969,54 @@ os.write(w,"ra") os.write(w,"nu") a.run() assert( _ == "Uranus" ) - + +a.restart() +a.run(wait=False) +os.write(w,"ra") +os.write(w,"nu") +a.run() +assert( _ == "Uranus" ) + += Automaton test interception_points, and restart +~ automaton +class ATMT9(Automaton): + def my_send(self, x): + self.io.loop.send(x) + @ATMT.state(initial=1) + def BEGIN(self): + self.res = "V" + self.send(Raw("ENU")) + @ATMT.ioevent(BEGIN, name="loop") + def received_sth(self, fd): + self.res += fd.recv().load + raise self.END() + @ATMT.state(final=1) + def END(self): + self.res += "s" + return self.res + +a=ATMT9(debug=5) +a.run() +assert( _ == "VENUs" ) + +a.restart() +a.run() +assert( _ == "VENUs" ) + +a.restart() +a.BEGIN.intercepts() +while True: + try: + x = a.run() + except Automaton.InterceptionPoint,p: + a.accept_packet(Raw(p.packet.load.lower()), wait=False) + else: + break + +x +assert( _ == "Venus" ) + + + Test IP options From d70e1097f813c8bd82046e9e97b2daff0b37a5c1 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 00:25:52 +0200 Subject: [PATCH 68/92] Made automaton thread exceptions raised by foreground task include traceback --- scapy/automaton.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 7d0aae48a..9e1f6f473 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -4,7 +4,7 @@ ## This program is published under a GPLv2 license from __future__ import with_statement -import types,itertools,time,os +import types,itertools,time,os,sys from select import select from collections import deque import thread @@ -496,7 +496,7 @@ class Automaton: self.cmdout.send(c) except Exception,e: self.debug(3, "Transfering exception [%s] from tid=%i"% (e,self.threadid)) - m = Message(type = _ATMT_Command.EXCEPTION, exception=e) + m = Message(type = _ATMT_Command.EXCEPTION, exception=e, exc_info=sys.exc_info()) self.cmdout.send(m) self.debug(3, "Stopping control thread (tid=%i)"%self.threadid) self.threadid = None @@ -627,7 +627,7 @@ class Automaton: elif c.type == _ATMT_Command.SINGLESTEP: raise self.Breakpoint("singlestep", state=c.state.state) elif c.type == _ATMT_Command.EXCEPTION: - raise c.exception + raise c.exc_info[0],c.exc_info[1],c.exc_info[2] def runbg(self, resume=None, wait=False): self.run(resume, wait) From eedbf833588df6125d04acf24215a0ed87eecbe7 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 12:50:31 +0200 Subject: [PATCH 69/92] Removed directory added by error in [c3b71e4375b8] --- graphics/ATMT_HelloWorld.png | Bin 7222 -> 0 bytes graphics/ATMT_TFTP_read.png | Bin 31887 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 graphics/ATMT_HelloWorld.png delete mode 100644 graphics/ATMT_TFTP_read.png diff --git a/graphics/ATMT_HelloWorld.png b/graphics/ATMT_HelloWorld.png deleted file mode 100644 index e5b5ddb188a64bba6f71d07ff850d4f9601a0ce5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7222 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 diff --git a/graphics/ATMT_TFTP_read.png b/graphics/ATMT_TFTP_read.png deleted file mode 100644 index 50621d976ac1addc852537ac49343ffd0e9e7209..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31887 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 z Date: Tue, 5 May 2009 14:50:43 +0200 Subject: [PATCH 70/92] Set Automaton._IO_fdwrapper.read()/recv() default read size to 65535 --- scapy/automaton.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 9e1f6f473..913326666 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -302,11 +302,11 @@ class Automaton: self.wr = wr def fileno(self): return self.rd - def read(self, n): + def read(self, n=65535): return os.read(self.rd, n) def write(self, msg): return os.write(self.wr,msg) - def recv(self, n): + def recv(self, n=65535): return self.read(n) def send(self, msg): return self.write(msg) From 8c0b841073b8e95e315186b87962834230a2173c Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 15:33:31 +0200 Subject: [PATCH 71/92] Fix typo in Automaton.remove_breakpoints --- scapy/automaton.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 913326666..bfdd2100d 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -604,7 +604,7 @@ class Automaton: for bp in bps: if hasattr(bp,"atmt_state"): bp = bp.atmt_state - self.breakpoints.discard(pb) + self.breakpoints.discard(bp) def start(self, *args, **kargs): if not self.running.locked(): From 7e356b7a8c34fb4a64db76482b96c37738b6d285 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 15:36:37 +0200 Subject: [PATCH 72/92] Add .unbreaks() and .unintercepts() to _instance_state Automaton's instance method wrapper --- scapy/automaton.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index bfdd2100d..8727d3156 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -36,7 +36,7 @@ class Message: for (k,v) in self.__dict__.iteritems() if not k.startswith("_")) -class instance_state: +class _instance_state: def __init__(self, instance): self.im_self = instance.im_self self.im_func = instance.im_func @@ -50,6 +50,10 @@ class instance_state: return self.im_self.add_breakpoints(self.im_func) def intercepts(self): return self.im_self.add_interception_points(self.im_func) + def unbreaks(self): + return self.im_self.remove_breakpoints(self.im_func) + def unintercepts(self): + return self.im_self.remove_interception_points(self.im_func) ############## @@ -420,7 +424,7 @@ class Automaton: for stname in self.states: setattr(self, stname, - instance_state(getattr(self, stname))) + _instance_state(getattr(self, stname))) self.parse_args(*args, **kargs) From 5f5ae750cac6a647ba76b8c6b32885a9c5a9d8a7 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 15:59:18 +0200 Subject: [PATCH 73/92] Fixed automaton breakpoint mechanism --- scapy/automaton.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 8727d3156..bff14d5d6 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -164,6 +164,7 @@ class _ATMT_Command: END = "END" EXCEPTION = "EXCEPTION" SINGLESTEP = "SINGLESTEP" + BREAKPOINT = "BREAKPOINT" INTERCEPT = "INTERCEPT" ACCEPT = "ACCEPT" REPLACE = "REPLACE" @@ -491,6 +492,10 @@ class Automaton: state = iterator.next() if isinstance(state, self.CommandMessage): break + elif isinstance(state, self.Breakpoint): + c = Message(type=_ATMT_Command.BREAKPOINT,state=state) + self.cmdout.send(c) + break if singlestep: c = Message(type=_ATMT_Command.SINGLESTEP,state=state) self.cmdout.send(c) @@ -513,7 +518,7 @@ class Automaton: # Entering a new state. First, call new state function if self.state.state in self.breakpoints and self.state.state != self.breakpointed: self.breakpointed = self.state.state - raise self.Breakpoint("breakpoint triggered on state %s" % self.state.state, + yield self.Breakpoint("breakpoint triggered on state %s" % self.state.state, state = self.state.state) self.breakpointed = None state_output = self.state.run() @@ -629,7 +634,9 @@ class Automaton: elif c.type == _ATMT_Command.INTERCEPT: raise self.InterceptionPoint("packet intercepted", state=c.state.state, packet=c.pkt) elif c.type == _ATMT_Command.SINGLESTEP: - raise self.Breakpoint("singlestep", state=c.state.state) + raise self.Breakpoint("singlestep state=[%s]"%c.state.state, state=c.state.state) + elif c.type == _ATMT_Command.BREAKPOINT: + raise self.Breakpoint("breakpoint triggered on state [%s]"%c.state.state, state=c.state.state) elif c.type == _ATMT_Command.EXCEPTION: raise c.exc_info[0],c.exc_info[1],c.exc_info[2] From de9e74adf0e5f174f71703f136b6e68bc688d1ad Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 16:02:48 +0200 Subject: [PATCH 74/92] Added Automaton.Singlepoint exception instead of using Automaton.Breakpoint --- scapy/automaton.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index bff14d5d6..1a4a1d9cd 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -351,6 +351,8 @@ class Automaton: class Breakpoint(AutomatonStopped): pass + class Singlestep(AutomatonStopped): + pass class InterceptionPoint(AutomatonStopped): def __init__(self, msg, state, packet): Automaton.AutomatonStopped.__init__(self, msg, state) @@ -634,7 +636,7 @@ class Automaton: elif c.type == _ATMT_Command.INTERCEPT: raise self.InterceptionPoint("packet intercepted", state=c.state.state, packet=c.pkt) elif c.type == _ATMT_Command.SINGLESTEP: - raise self.Breakpoint("singlestep state=[%s]"%c.state.state, state=c.state.state) + raise self.Singlestep("singlestep state=[%s]"%c.state.state, state=c.state.state) elif c.type == _ATMT_Command.BREAKPOINT: raise self.Breakpoint("breakpoint triggered on state [%s]"%c.state.state, state=c.state.state) elif c.type == _ATMT_Command.EXCEPTION: From 04b25e4f87b76116dd053e03c7df64c29a9d59e7 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 16:08:34 +0200 Subject: [PATCH 75/92] Added a desctructor to Automaton --- scapy/automaton.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scapy/automaton.py b/scapy/automaton.py index 1a4a1d9cd..4238dcbc8 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -436,6 +436,9 @@ class Automaton: def __iter__(self): return self + def __del__(self): + self.stop() + def _run_condition(self, cond, *args, **kargs): try: self.debug(5, "Trying %s [%s]" % (cond.atmt_type, cond.atmt_condname)) From 5c093b92a28956153cf51b55da4cee0a234e33d4 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 16:20:21 +0200 Subject: [PATCH 76/92] Reorganized Automaton' exceptions hierarchy --- scapy/automaton.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 4238dcbc8..847c4bd6f 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -335,27 +335,25 @@ class Automaton: class AutomatonException(Exception): - pass - - class ErrorState(AutomatonException): - def __init__(self, msg, result=None): - Exception.__init__(self, msg) - self.result = result - class Stuck(ErrorState): - pass - - class AutomatonStopped(AutomatonException): - def __init__(self, msg, state=None): + def __init__(self, msg, state=None, result=None): Exception.__init__(self, msg) self.state = state + self.result = result + + class ErrorState(AutomatonException): + pass + class Stuck(AutomatonException): + pass + class AutomatonStopped(AutomatonException): + pass class Breakpoint(AutomatonStopped): pass class Singlestep(AutomatonStopped): pass class InterceptionPoint(AutomatonStopped): - def __init__(self, msg, state, packet): - Automaton.AutomatonStopped.__init__(self, msg, state) + def __init__(self, msg, state=None, result=None, packet=None): + Automaton.AutomatonStopped.__init__(self, msg, state=state, result=result) self.packet = packet class CommandMessage(AutomatonException): @@ -528,7 +526,8 @@ class Automaton: self.breakpointed = None state_output = self.state.run() if self.state.error: - raise self.ErrorState("Reached %s: [%r]" % (self.state.state, state_output), result=state_output) + raise self.ErrorState("Reached %s: [%r]" % (self.state.state, state_output), + result=state_output, state=self.state.state) if self.state.final: raise StopIteration(state_output) @@ -545,7 +544,8 @@ class Automaton: if ( len(self.recv_conditions[self.state.state]) == 0 and len(self.ioevents[self.state.state]) == 0 and len(self.timeout[self.state.state]) == 1 ): - raise self.Stuck("stuck in [%s]" % self.state.state,result=state_output) + raise self.Stuck("stuck in [%s]" % self.state.state, + state=self.state.state, result=state_output) # Finally listen and pay attention to timeouts expirations = iter(self.timeout[self.state.state]) @@ -574,7 +574,7 @@ class Automaton: for fd in r: self.debug(5, "Looking at %r" % fd) if fd == self.cmdin: - yield self.CommandMessage() + yield self.CommandMessage("Received command message") elif fd == self.listen_sock: pkt = self.listen_sock.recv(MTU) if pkt is not None: From 5fdffeb361e33fdf55488e472b170e892600ca6e Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 16:44:25 +0200 Subject: [PATCH 77/92] Raise an exception for automaton unknown interception verdict --- scapy/automaton.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 847c4bd6f..c5633b8e2 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -340,6 +340,8 @@ class Automaton: self.state = state self.result = result + class AutomatonError(AutomatonException): + pass class ErrorState(AutomatonException): pass class Stuck(AutomatonException): @@ -380,7 +382,7 @@ class Automaton: elif cmd.type == _ATMT_Command.ACCEPT: self.debug(3,"INTERCEPT: packet accepted") else: - self.debug(1,"INTERCEPT: unkown verdict: %r" % cmd.type) + raise self.AutomatonError("INTERCEPT: unkown verdict: %r" % cmd.type) self.my_send(pkt) self.debug(3,"SENT : %s" % pkt.summary()) self.packets.append(pkt.copy()) From 32a0f1482418498a69d3b088e8b7c432f1e7c9f4 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 16:45:35 +0200 Subject: [PATCH 78/92] Changed Automaton.accept_packet() and reject_packet() to not wait by default --- scapy/automaton.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index c5633b8e2..e4160dc98 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -668,7 +668,7 @@ class Automaton: self.stop() self.start(*args, **kargs) - def accept_packet(self, pkt=None, wait=True): + def accept_packet(self, pkt=None, wait=False): rsm = Message() if pkt is None: rsm.type = _ATMT_Command.ACCEPT @@ -677,7 +677,7 @@ class Automaton: rsm.pkt = pkt return self.run(resume=rsm, wait=wait) - def reject_packet(self, wait=True): + def reject_packet(self, wait=False): rsm = Message(type = _ATMT_Command.REJECT) return self.run(resume=rsm, wait=wait) From 1adab0384fd32749422c95aa9f980477e40b771c Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 17:04:47 +0200 Subject: [PATCH 79/92] Added Automaton.intercepted_packet attribute to have packet handy while automaton is waiting for a verdict --- scapy/automaton.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scapy/automaton.py b/scapy/automaton.py index e4160dc98..63928efab 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -370,9 +370,11 @@ class Automaton: def send(self, pkt): if self.state.state in self.interception_points: self.debug(3,"INTERCEPT: packet intercepted: %s" % pkt.summary()) + self.intercepted_packet = pkt cmd = Message(type = _ATMT_Command.INTERCEPT, state=self.state, pkt=pkt) self.cmdout.send(cmd) cmd = self.cmdin.recv() + self.intercepted_packet = None if cmd.type == _ATMT_Command.REJECT: self.debug(3,"INTERCEPT: packet rejected") return @@ -395,6 +397,7 @@ class Automaton: self.breakpointed = None self.breakpoints = set() self.interception_points = set() + self.intercepted_packet = None self.debug_level=0 self.init_args=args self.init_kargs=kargs From 56e5a3f2871433a03b99f6ab87ebe94d57fb8779 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 5 May 2009 17:05:18 +0200 Subject: [PATCH 80/92] Renamed Automaton.running to Automaton.started to better reflect documented automaton state --- scapy/automaton.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 63928efab..b55a2c31a 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -392,7 +392,7 @@ class Automaton: ## Internals def __init__(self, external_fd={}, *args, **kargs): - self.running = thread.allocate_lock() + self.started = thread.allocate_lock() self.threadid = None self.breakpointed = None self.breakpoints = set() @@ -466,7 +466,7 @@ class Automaton: def _do_control(self, *args, **kargs): - with self.running: + with self.started: self.threadid = thread.get_ident() # Update default parameters @@ -626,7 +626,7 @@ class Automaton: self.breakpoints.discard(bp) def start(self, *args, **kargs): - if not self.running.locked(): + if not self.started.locked(): self._do_start(*args, **kargs) def run(self, resume=None, wait=True): @@ -658,7 +658,7 @@ class Automaton: def stop(self): self.cmdin.send(Message(type=_ATMT_Command.STOP)) - with self.running: + with self.started: # Flush command pipes while True: r,_,_ = select([self.cmdin, self.cmdout],[],[],0) From bd5eb8dfdb433c85bb28a4d8f6d204dbf6fe0020 Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 6 May 2009 16:00:03 +0200 Subject: [PATCH 81/92] Fixed first positional parameter eaten by external_fd in Automaton.__init__ --- scapy/automaton.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index b55a2c31a..228bf1823 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -391,7 +391,8 @@ class Automaton: ## Internals - def __init__(self, external_fd={}, *args, **kargs): + def __init__(self, *args, **kargs): + external_fd = kargs.pop("external_fd",{}) self.started = thread.allocate_lock() self.threadid = None self.breakpointed = None From f13602150438b4afd00a46f1dafb4062369cc835 Mon Sep 17 00:00:00 2001 From: Phil Date: Sat, 16 May 2009 16:35:32 +0200 Subject: [PATCH 82/92] Added packet list's session extractor --- scapy/plist.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/scapy/plist.py b/scapy/plist.py index bc5c24f56..89b6af03e 100644 --- a/scapy/plist.py +++ b/scapy/plist.py @@ -7,6 +7,7 @@ import os from config import conf from base_classes import BasePacket,BasePacketList from packet import Padding +from collections import defaultdict from utils import do_graph,hexdump,make_table,make_lined_table,make_tex_table,get_temp_file @@ -406,6 +407,32 @@ lfilter: truth function to apply to each packet to decide whether it will be dis if multi: remain = filter(lambda x:not hasattr(x,"_answered"), remain) return SndRcvList(sr),PacketList(remain) + + def sessions(self, session_extractor=None): + if session_extractor is None: + def session_extractor(p): + if 'Ether' in p: + if 'IP' in p: + if 'TCP' in p: + sess = p.sprintf("TCP %IP.src%:%r,TCP.sport% > %IP.dst%:%r,TCP.dport%") + elif 'UDP' in p: + sess = p.sprintf("UDP %IP.src%:%r,UDP.sport% > %IP.dst%:%r,UDP.dport%") + elif 'ICMP' in p: + sess = p.sprintf("ICMP %IP.src% > %IP.dst% type=%r,ICMP.type% code=%r,ICMP.code% id=%ICMP.id%") + else: + sess = p.sprintf("IP %IP.src% > %IP.dst% proto=%IP.proto%") + elif 'ARP' in p: + sess = p.sprintf("ARP %ARP.psrc% > %ARP.pdst%") + else: + sess = p.sprintf("Ethernet type=%04xr,Ether.type%") + return sess + sessions = defaultdict(self.__class__) + for p in self.res: + sess = session_extractor(self._elt2pkt(p)) + sessions[sess].append(p) + return dict(sessions) + + From 8cd8caea2cbda8001f31ad0f01835d765691ebfe Mon Sep 17 00:00:00 2001 From: Phil Date: Sat, 16 May 2009 16:35:32 +0200 Subject: [PATCH 83/92] Added PacketList.replace() method to change designated fields' values --- scapy/plist.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/scapy/plist.py b/scapy/plist.py index 89b6af03e..afed77fc3 100644 --- a/scapy/plist.py +++ b/scapy/plist.py @@ -431,7 +431,42 @@ lfilter: truth function to apply to each packet to decide whether it will be dis sess = session_extractor(self._elt2pkt(p)) sessions[sess].append(p) return dict(sessions) + + def replace(self, *args, **kargs): + """ + lst.replace(,[,]) + lst.replace( (fld,[ov],nv),(fld,[ov,]nv),...) + if ov is None, all values are replaced + ex: + lst.replace( IP.src, "192.168.1.1", "10.0.0.1" ) + lst.replace( IP.ttl, 64 ) + lst.replace( (IP.ttl, 64), (TCP.sport, 666, 777), ) + """ + delete_checksums = kargs.get("delete_checksums",False) + x=PacketList(name="Replaced %s" % self.listname) + if type(args[0]) is not tuple: + args = (args,) + for p in self.res: + p = self._elt2pkt(p) + copied = False + for scheme in args: + fld = scheme[0] + old = scheme[1] # not used if len(scheme) == 2 + new = scheme[-1] + for o in fld.owners: + if o in p: + if len(scheme) == 2 or p[o].getfieldval(fld.name) == old: + if not copied: + p = p.copy() + if delete_checksums: + p.delete_checksums() + copied = True + setattr(p[o], fld.name, new) + x.append(p) + return x + + From ac10f4ec8a29f4575507ebd0ca56f962089cec3d Mon Sep 17 00:00:00 2001 From: Phil Date: Sat, 16 May 2009 16:35:32 +0200 Subject: [PATCH 84/92] Have X..Fields whose value is None shown as "None" instead of "0x0" Finishes the work of [a15cf102ff2b] --- scapy/fields.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/scapy/fields.py b/scapy/fields.py index a8793d1f6..a174137af 100644 --- a/scapy/fields.py +++ b/scapy/fields.py @@ -249,8 +249,6 @@ class ByteField(Field): class XByteField(ByteField): def i2repr(self, pkt, x): - if x is None: - x = 0 return lhex(self.i2h(pkt, x)) class X3BytesField(XByteField): @@ -272,8 +270,6 @@ class LEShortField(Field): class XShortField(ShortField): def i2repr(self, pkt, x): - if x is None: - x = 0 return lhex(self.i2h(pkt, x)) @@ -299,8 +295,6 @@ class LESignedIntField(Field): class XIntField(IntField): def i2repr(self, pkt, x): - if x is None: - x = 0 return lhex(self.i2h(pkt, x)) @@ -310,8 +304,6 @@ class LongField(Field): class XLongField(LongField): def i2repr(self, pkt, x): - if x is None: - x = 0 return lhex(self.i2h(pkt, x)) class IEEEFloatField(Field): From 2c3b36e5eb037786f28f9242efdb3c0f29676bf0 Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 14 Jun 2009 01:20:26 +0200 Subject: [PATCH 85/92] Fix automaton external fd interpretation If only one fd is given instead of a couple, it is used as read/write instead of read while no fd is used for writing --- scapy/automaton.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 228bf1823..8ef8bd61f 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -411,7 +411,7 @@ class Automaton: for n in self.ionames: extfd = external_fd.get(n) if type(extfd) is not tuple: - extfd = (extfd,None) + extfd = (extfd,extfd) ioin,ioout = extfd if ioin is None: ioin = ObjectPipe() From 1526068d8950d0b92d6a7104ef410e547a2f817e Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 14 Jun 2009 01:22:18 +0200 Subject: [PATCH 86/92] Added 'as_supersocket' argument to automaton ioevent() decorator Provided name will be used to create a class method that will instantiate the automaton and wrap it into a supersocket where the ioevent will be the link layer --- scapy/automaton.py | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 8ef8bd61f..5779bb24b 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -137,13 +137,14 @@ class ATMT: return f return deco @staticmethod - def ioevent(state, name, prio=0): + def ioevent(state, name, prio=0, as_supersocket=None): def deco(f, state=state): f.atmt_type = ATMT.IOEVENT f.atmt_state = state.atmt_state f.atmt_condname = f.func_name f.atmt_ioname = name f.atmt_prio = prio + f.atmt_as_supersocket = as_supersocket return f return deco @staticmethod @@ -170,6 +171,36 @@ class _ATMT_Command: REPLACE = "REPLACE" REJECT = "REJECT" +class _ATMT_supersocket: + def __init__(self, name, ioevent, automaton, proto, args, kargs): + self.name = name + self.ioevent = ioevent + self.proto = proto + self.spa,self.spb = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM) + kargs["external_fd"] = {ioevent:self.spb} + self.atmt = automaton(*args, **kargs) + self.atmt.runbg() + def fileno(self): + return self.spa.fileno() + def send(self, s): + if type(s) is not str: + s = str(s) + return self.spa.send(s) + def recv(self, n=None): + r = self.spa.recv(n) + if self.proto is not None: + r = self.proto(r) + return r + + +class _ATMT_to_supersocket: + def __init__(self, name, ioevent, automaton): + self.name = name + self.ioevent = ioevent + self.automaton = automaton + def __call__(self, proto, *args, **kargs): + return _ATMT_supersocket(self.name, self.ioevent, self.automaton, proto, args, kargs) + class Automaton_metaclass(type): def __new__(cls, name, bases, dct): cls = super(Automaton_metaclass, cls).__new__(cls, name, bases, dct) @@ -182,6 +213,7 @@ class Automaton_metaclass(type): cls.actions={} cls.initial_states=[] cls.ionames = [] + cls.iosupersockets = [] members = {} classes = [cls] @@ -216,6 +248,8 @@ class Automaton_metaclass(type): elif m.atmt_type == ATMT.IOEVENT: cls.ioevents[m.atmt_state].append(m) cls.ionames.append(m.atmt_ioname) + if m.atmt_as_supersocket is not None: + cls.iosupersockets.append(m) elif m.atmt_type == ATMT.TIMEOUT: cls.timeout[m.atmt_state].append((m.atmt_timeout, m)) elif m.atmt_type == ATMT.ACTION: @@ -233,9 +267,11 @@ class Automaton_metaclass(type): for condname,actlst in cls.actions.iteritems(): actlst.sort(lambda c1,c2: cmp(c1.atmt_cond[condname], c2.atmt_cond[condname])) + for ioev in cls.iosupersockets: + setattr(cls, ioev.atmt_as_supersocket, _ATMT_to_supersocket(ioev.atmt_as_supersocket, ioev.atmt_ioname, cls)) + return cls - def graph(self, **kargs): s = 'digraph "%s" {\n' % self.__class__.__name__ From 14fdac1e2e699e02f4ac950813b731bb128e1db6 Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 14 Jun 2009 01:22:20 +0200 Subject: [PATCH 87/92] Added simple TCP client automaton --- scapy/layers/inet.py | 128 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/scapy/layers/inet.py b/scapy/layers/inet.py index ee3a163bd..723ca527b 100644 --- a/scapy/layers/inet.py +++ b/scapy/layers/inet.py @@ -14,6 +14,7 @@ from scapy.packet import * from scapy.volatile import * from scapy.sendrecv import sr,sr1,srp1 from scapy.plist import PacketList,SndRcvList +from scapy.automaton import Automaton,ATMT import scapy.as_resolvers @@ -1302,6 +1303,133 @@ traceroute(target, [maxttl=30,] [dport=80,] [sport=80,] [verbose=conf.verb]) -> +############################# +## Simple TCP client stack ## +############################# + +class TCP_client(Automaton): + + def parse_args(self, ip, port, *args, **kargs): + self.dst = ip + self.dport = port + self.sport = random.randrange(0,2**16) + self.l4 = IP(dst=ip)/TCP(sport=self.sport, dport=self.dport, flags=0, + seq=random.randrange(0,2**32)) + self.src = self.l4.src + self.swin=self.l4[TCP].window + self.dwin=1 + self.rcvbuf="" + bpf = "host %s and host %s and port %i and port %i" % (self.src, + self.dst, + self.sport, + self.dport) + +# bpf=None + Automaton.parse_args(self, filter=bpf, **kargs) + + + def master_filter(self, pkt): + return (IP in pkt and + pkt[IP].src == self.dst and + pkt[IP].dst == self.src and + TCP in pkt and + pkt[TCP].sport == self.dport and + pkt[TCP].dport == self.sport and + self.l4[TCP].seq >= pkt[TCP].ack and # XXX: seq/ack 2^32 wrap up + ((self.l4[TCP].ack == 0) or (self.l4[TCP].ack <= pkt[TCP].seq <= self.l4[TCP].ack+self.swin)) ) + + + @ATMT.state(initial=1) + def START(self): + pass + + @ATMT.state() + def SYN_SENT(self): + pass + + @ATMT.state() + def ESTABLISHED(self): + pass + + @ATMT.state() + def LAST_ACK(self): + pass + + @ATMT.state(final=1) + def CLOSED(self): + pass + + + @ATMT.condition(START) + def connect(self): + raise self.SYN_SENT() + @ATMT.action(connect) + def send_syn(self): + self.l4[TCP].flags = "S" + self.send(self.l4) + self.l4[TCP].seq += 1 + + + @ATMT.receive_condition(SYN_SENT) + def synack_received(self, pkt): + if pkt[TCP].flags & 0x3f == 0x12: + raise self.ESTABLISHED().action_parameters(pkt) + @ATMT.action(synack_received) + def send_ack_of_synack(self, pkt): + self.l4[TCP].ack = pkt[TCP].seq+1 + self.l4[TCP].flags = "A" + self.send(self.l4) + + @ATMT.receive_condition(ESTABLISHED) + def incoming_data_received(self, pkt): + if not isinstance(pkt[TCP].payload, NoPayload) and not isinstance(pkt[TCP].payload, Padding): + raise self.ESTABLISHED().action_parameters(pkt) + @ATMT.action(incoming_data_received) + def receive_data(self,pkt): + data = str(pkt[TCP].payload) + if data and self.l4[TCP].ack == pkt[TCP].seq: + self.l4[TCP].ack += len(data) + self.l4[TCP].flags = "A" + self.send(self.l4) + self.rcvbuf += data + if pkt[TCP].flags & 8 != 0: #PUSH + self.oi.tcp.send(self.rcvbuf) + self.rcvbuf = "" + + @ATMT.ioevent(ESTABLISHED,name="tcp", as_supersocket="tcplink") + def outgoing_data_received(self, fd): + raise self.ESTABLISHED().action_parameters(fd.recv()) + @ATMT.action(outgoing_data_received) + def send_data(self, d): + self.l4[TCP].flags = "PA" + self.send(self.l4/d) + self.l4[TCP].seq += len(d) + + + @ATMT.receive_condition(ESTABLISHED) + def reset_received(self, pkt): + if pkt[TCP].flags & 4 != 0: + raise self.CLOSED() + + @ATMT.receive_condition(ESTABLISHED) + def fin_received(self, pkt): + if pkt[TCP].flags & 0x1 == 1: + raise self.LAST_ACK().action_parameters(pkt) + @ATMT.action(fin_received) + def send_finack(self, pkt): + self.l4[TCP].flags = "FA" + self.l4[TCP].ack = pkt[TCP].seq+1 + self.send(self.l4) + self.l4[TCP].seq += 1 + + @ATMT.receive_condition(LAST_ACK) + def ack_of_fin_received(self, pkt): + if pkt[TCP].flags & 0x3f == 0x10: + raise self.CLOSED() + + + + ##################### ## Reporting stuff ## ##################### From aa5c67f83060bfe7d20f4a5e432626e5c8724a4b Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 14 Jun 2009 01:22:54 +0200 Subject: [PATCH 88/92] Fixed automaton ioevent fd wrapping --- scapy/automaton.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 5779bb24b..b66dc9038 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -451,12 +451,12 @@ class Automaton: ioin,ioout = extfd if ioin is None: ioin = ObjectPipe() - elif type(ioin) is int: + elif type(ioin) is not types.InstanceType: ioin = self._IO_fdwrapper(ioin,None) if ioout is None: ioout = ObjectPipe() - elif type(ioout) is int: - ioin = self._IO_fdwrapper(None,ioout) + elif type(ioout) is not types.InstanceType: + ioout = self._IO_fdwrapper(None,ioout) self.ioin[n] = ioin self.ioout[n] = ioout From bf7034dd1cae1642f3eca27e99f851087bc4224c Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 14 Jun 2009 01:23:46 +0200 Subject: [PATCH 89/92] Added 'll' parameter to automata to provide link layer supersocket to use --- scapy/automaton.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index b66dc9038..072da9e1d 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -429,6 +429,7 @@ class Automaton: ## Internals def __init__(self, *args, **kargs): external_fd = kargs.pop("external_fd",{}) + self.send_sock_class = kargs.pop("ll", conf.L3socket) self.started = thread.allocate_lock() self.threadid = None self.breakpointed = None @@ -514,7 +515,7 @@ class Automaton: # Start the automaton self.state=self.initial_states[0](self) - self.send_sock = conf.L3socket() + self.send_sock = self.send_sock_class() self.listen_sock = conf.L2listen(**self.socket_kargs) self.packets = PacketList(name="session[%s]"%self.__class__.__name__) From 4fa90e6f14727287650ad46d599e4634b41a16dc Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 14 Jun 2009 01:25:06 +0200 Subject: [PATCH 90/92] Changed sndrcv() interal function to stop returning unused third packet list --- scapy/layers/bluetooth.py | 2 +- scapy/sendrecv.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scapy/layers/bluetooth.py b/scapy/layers/bluetooth.py index cfa874f53..86b9b277a 100644 --- a/scapy/layers/bluetooth.py +++ b/scapy/layers/bluetooth.py @@ -189,7 +189,7 @@ class BluetoothHCISocket(SuperSocket): def srbt(peer, pkts, inter=0.1, *args, **kargs): """send and receive using a bluetooth socket""" s = conf.BTsocket(peer=peer) - a,b,c=sndrcv(s,pkts,inter=inter,*args,**kargs) + a,b = sndrcv(s,pkts,inter=inter,*args,**kargs) s.close() return a,b diff --git a/scapy/sendrecv.py b/scapy/sendrecv.py index 09c91881b..b0432686d 100644 --- a/scapy/sendrecv.py +++ b/scapy/sendrecv.py @@ -31,7 +31,7 @@ class debug: -def sndrcv(pks, pkt, timeout = 2, inter = 0, verbose=None, chainCC=0, retry=0, multi=0): +def sndrcv(pks, pkt, timeout = None, inter = 0, verbose=None, chainCC=0, retry=0, multi=0): if not isinstance(pkt, Gen): pkt = SetGen(pkt) @@ -200,7 +200,7 @@ def sndrcv(pks, pkt, timeout = 2, inter = 0, verbose=None, chainCC=0, retry=0, m if verbose: print "\nReceived %i packets, got %i answers, remaining %i packets" % (nbrecv+len(ans), len(ans), notans) - return plist.SndRcvList(ans),plist.PacketList(remain,"Unanswered"),debug.recv + return plist.SndRcvList(ans),plist.PacketList(remain,"Unanswered") def __gen_send(s, x, inter=0, loop=0, count=None, verbose=None, *args, **kargs): @@ -298,7 +298,7 @@ iface: listen answers only on the given interface""" if not kargs.has_key("timeout"): kargs["timeout"] = -1 s = conf.L3socket(filter=filter, iface=iface, nofilter=nofilter) - a,b,c=sndrcv(s,x,*args,**kargs) + a,b=sndrcv(s,x,*args,**kargs) s.close() return a,b @@ -316,7 +316,7 @@ iface: listen answers only on the given interface""" if not kargs.has_key("timeout"): kargs["timeout"] = -1 s=conf.L3socket(filter=filter, nofilter=nofilter, iface=iface) - a,b,c=sndrcv(s,x,*args,**kargs) + a,b=sndrcv(s,x,*args,**kargs) s.close() if len(a) > 0: return a[0][1] @@ -339,7 +339,7 @@ iface: work only on the given interface""" if iface is None and iface_hint is not None: iface = conf.route.route(iface_hint)[0] s = conf.L2socket(iface=iface, filter=filter, nofilter=nofilter, type=type) - a,b,c=sndrcv(s ,x,*args,**kargs) + a,b=sndrcv(s ,x,*args,**kargs) s.close() return a,b From e118a2311d6f9ec964a29859d4b23eaa85dc971f Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 14 Jun 2009 01:25:08 +0200 Subject: [PATCH 91/92] Added .sr() method to supersockets --- scapy/automaton.py | 14 ++++++++++++-- scapy/supersocket.py | 12 ++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/scapy/automaton.py b/scapy/automaton.py index 072da9e1d..62ce75238 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -13,6 +13,7 @@ from utils import do_graph from error import log_interactive from plist import PacketList from data import MTU +from supersocket import SuperSocket class ObjectPipe: def __init__(self): @@ -171,7 +172,7 @@ class _ATMT_Command: REPLACE = "REPLACE" REJECT = "REJECT" -class _ATMT_supersocket: +class _ATMT_supersocket(SuperSocket): def __init__(self, name, ioevent, automaton, proto, args, kargs): self.name = name self.ioevent = ioevent @@ -191,7 +192,16 @@ class _ATMT_supersocket: if self.proto is not None: r = self.proto(r) return r - + def close(self): + pass + def sr(self, *args, **kargs): + return sndrcv(self, *args, **kargs) + def sr1(self, *args, **kargs): + a,b = sndrcv(self, *args, **kargs) + if len(a) > 0: + return a[0][1] + else: + return None class _ATMT_to_supersocket: def __init__(self, name, ioevent, automaton): diff --git a/scapy/supersocket.py b/scapy/supersocket.py index e3e49c061..10a32de95 100644 --- a/scapy/supersocket.py +++ b/scapy/supersocket.py @@ -40,10 +40,14 @@ class SuperSocket: self.outs.close() if self.ins and self.ins.fileno() != -1: self.ins.close() - def bind_in(self, addr): - self.ins.bind(addr) - def bind_out(self, addr): - self.outs.bind(addr) + def sr(self, *args, **kargs): + return sndrcv(self, *args, **kargs) + def sr1(self, *args, **kargs): + a,b = sndrcv(self, *args, **kargs) + if len(a) > 0: + return a[0][1] + else: + return None class L3RawSocket(SuperSocket): desc = "Layer 3 using Raw sockets (PF_INET/SOCK_RAW)" From d88f0f11dea1e9f05b91b1fa2eb1eb4163a731fc Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 14 Jun 2009 01:26:18 +0200 Subject: [PATCH 92/92] Changed default MTU to be 32767 and made supersocket.recv() argument optional --- scapy/arch/linux.py | 4 ++-- scapy/arch/pcapdnet.py | 4 ++-- scapy/automaton.py | 2 +- scapy/data.py | 2 +- scapy/layers/bluetooth.py | 3 ++- scapy/supersocket.py | 4 ++-- scapy/utils.py | 16 ++++++++-------- 7 files changed, 18 insertions(+), 17 deletions(-) diff --git a/scapy/arch/linux.py b/scapy/arch/linux.py index 41514766d..76a46fb02 100644 --- a/scapy/arch/linux.py +++ b/scapy/arch/linux.py @@ -333,7 +333,7 @@ class L3PacketSocket(SuperSocket): for i in self.iff: set_promisc(self.ins, i, 0) SuperSocket.close(self) - def recv(self, x): + def recv(self, x=MTU): pkt, sa_ll = self.ins.recvfrom(x) if sa_ll[2] == socket.PACKET_OUTGOING: return None @@ -419,7 +419,7 @@ class L2Socket(SuperSocket): self.LL = conf.default_l2 warning("Unable to guess type (interface=%s protocol=%#x family=%i). Using %s" % (sa_ll[0],sa_ll[1],sa_ll[3],self.LL.name)) - def recv(self, x): + def recv(self, x=MTU): pkt, sa_ll = self.ins.recvfrom(x) if sa_ll[2] == socket.PACKET_OUTGOING: return None diff --git a/scapy/arch/pcapdnet.py b/scapy/arch/pcapdnet.py index d5b70d543..b062c8aef 100644 --- a/scapy/arch/pcapdnet.py +++ b/scapy/arch/pcapdnet.py @@ -101,7 +101,7 @@ if conf.use_pcap: def close(self): del(self.ins) - def recv(self, x): + def recv(self, x=MTU): ll = self.ins.datalink() if ll in conf.l2types: cls = conf.l2types[ll] @@ -291,7 +291,7 @@ if conf.use_pcap and conf.use_dnet: if filter: self.ins.setfilter(filter) self.outs = dnet.eth(iface) - def recv(self,x): + def recv(self,x=MTU): ll = self.ins.datalink() if ll in conf.l2types: cls = conf.l2types[ll] diff --git a/scapy/automaton.py b/scapy/automaton.py index 62ce75238..66cf0cf9c 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -187,7 +187,7 @@ class _ATMT_supersocket(SuperSocket): if type(s) is not str: s = str(s) return self.spa.send(s) - def recv(self, n=None): + def recv(self, n=MTU): r = self.spa.recv(n) if self.proto is not None: r = self.proto(r) diff --git a/scapy/data.py b/scapy/data.py index 018b06297..fe4751d93 100644 --- a/scapy/data.py +++ b/scapy/data.py @@ -44,7 +44,7 @@ IPV6_ADDR_UNSPECIFIED = 0x10000 -MTU = 1600 +MTU = 0x7fff # a.k.a give me all you have # file parsing to get some values : diff --git a/scapy/layers/bluetooth.py b/scapy/layers/bluetooth.py index 86b9b277a..89757a98d 100644 --- a/scapy/layers/bluetooth.py +++ b/scapy/layers/bluetooth.py @@ -9,6 +9,7 @@ from scapy.config import conf from scapy.packet import * from scapy.fields import * from scapy.supersocket import SuperSocket +from scapy.data import MTU class HCI_Hdr(Packet): @@ -163,7 +164,7 @@ class BluetoothL2CAPSocket(SuperSocket): self.ins = self.outs = s - def recv(self, x): + def recv(self, x=MTU): return L2CAP_CmdHdr(self.ins.recv(x)) diff --git a/scapy/supersocket.py b/scapy/supersocket.py index 10a32de95..8afca6cf8 100644 --- a/scapy/supersocket.py +++ b/scapy/supersocket.py @@ -27,7 +27,7 @@ class SuperSocket: sx = str(x) x.sent_time = time.time() return self.outs.send(sx) - def recv(self, x): + def recv(self, x=MTU): return conf.raw_layer(self.ins.recv(x)) def fileno(self): return self.ins.fileno() @@ -55,7 +55,7 @@ class L3RawSocket(SuperSocket): self.outs = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) self.outs.setsockopt(socket.SOL_IP, socket.IP_HDRINCL, 1) self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type)) - def recv(self, x): + def recv(self, x=MTU): return Ether(self.ins.recv(x)).payload def send(self, x): try: diff --git a/scapy/utils.py b/scapy/utils.py index 960dfb71f..6d43a7a97 100644 --- a/scapy/utils.py +++ b/scapy/utils.py @@ -484,7 +484,7 @@ class RawPcapReader: return pkt - def read_packet(self): + def read_packet(self, size=MTU): """return a single packet read from the file returns None when no more packets are available @@ -493,7 +493,7 @@ class RawPcapReader: if len(hdr) < 16: return None sec,usec,caplen,wirelen = struct.unpack(self.endian+"IIII", hdr) - s = self.f.read(caplen) + s = self.f.read(caplen)[:MTU] return s,(sec,usec,wirelen) # caplen = len(s) @@ -519,10 +519,10 @@ class RawPcapReader: res.append(p) return res - def recv(self, size): + def recv(self, size=MTU): """ Emulate a socket """ - return self.read_packet()[0] + return self.read_packet(size)[0] def fileno(self): return self.f.fileno() @@ -540,8 +540,8 @@ class PcapReader(RawPcapReader): except KeyError: warning("PcapReader: unknown LL type [%i]/[%#x]. Using Raw packets" % (self.linktype,self.linktype)) self.LLcls = conf.raw_layer - def read_packet(self): - rp = RawPcapReader.read_packet(self) + def read_packet(self, size=MTU): + rp = RawPcapReader.read_packet(self,size) if rp is None: return None s,(sec,usec,wirelen) = rp @@ -560,8 +560,8 @@ class PcapReader(RawPcapReader): res = RawPcapReader.read_all(self, count) import plist return plist.PacketList(res,name = os.path.basename(self.filename)) - def recv(self, size): - return self.read_packet() + def recv(self, size=MTU): + return self.read_packet(size)