class ARP(pypacker.Packet): __hdr__ = ( ("hrd", "H", ARP_HRD_ETH), ("pro", "H", ARP_PRO_IP), ("hln", "B", 6), # hardware address length ("pln", "B", 4), # protocol address length ("op", "H", ARP_OP_REQUEST), ("sha", "6s", b"\x00" * 6), # sender mac ("spa", "4s", b"\x00" * 4), # sender ip ("tha", "6s", b"\x00" * 6), # target mac ("tpa", "4s", b"\x00" * 4) # target ip ) # convenient access sha_s = pypacker.get_property_mac("sha") spa_s = pypacker.get_property_ip4("spa") tha_s = pypacker.get_property_mac("tha") tpa_s = pypacker.get_property_ip4("tpa")
class IGMP(pypacker.Packet): __hdr__ = (("type", "B", 0), ("maxresp", "B", 0), ("sum", "H", 0, FIELD_FLAG_AUTOUPDATE), ("group", "4s", b"\x00" * 4)) # Convenient access for: group[_s] group_s = pypacker.get_property_ip4("group") def _update_fields(self): if self.sum_au_active and self._changed(): self.sum = 0 self.sum = checksum.in_cksum(pypacker.Packet.bin(self))
class IGMP(pypacker.Packet): __hdr__ = (("type", "B", 0), ("maxresp", "B", 0), ("sum", "H", 0, True), ("group", "4s", b"\x00" * 4)) # Convenient access for: group[_s] group_s = pypacker.get_property_ip4("group") def bin(self, update_auto_fields=True): if update_auto_fields and self.sum_au_active and self._changed(): self.sum = 0 self.sum = checksum.in_cksum(pypacker.Packet.bin(self)) return pypacker.Packet.bin(self, update_auto_fields=update_auto_fields)
class IP(pypacker.Packet): __hdr__ = ( ("v_hl", "B", 69, FIELD_FLAG_AUTOUPDATE), # = 0x45 ("tos", "B", 0), ("len", "H", 20, FIELD_FLAG_AUTOUPDATE), ("id", "H", 0), # TODO: rename to frag_off ("off", "H", 0), ("ttl", "B", 64), ("p", "B", IP_PROTO_TCP, FIELD_FLAG_AUTOUPDATE | FIELD_FLAG_IS_TYPEFIELD), ("sum", "H", 0, FIELD_FLAG_AUTOUPDATE), ("src", "4s", b"\x00" * 4), ("dst", "4s", b"\x00" * 4), ("opts", None, triggerlist.TriggerList)) __handler__ = { IP_PROTO_ICMP: icmp.ICMP, IP_PROTO_IGMP: igmp.IGMP, IP_PROTO_TCP: tcp.TCP, IP_PROTO_UDP: udp.UDP, IP_PROTO_IP6: ip6.IP6, IP_PROTO_ESP: esp.ESP, IP_PROTO_PIM: pim.PIM, IP_PROTO_IPXIP: ipx.IPX, IP_PROTO_SCTP: sctp.SCTP, IP_PROTO_OSPF: ospf.OSPF } def __get_v(self): return self.v_hl >> 4 def __set_v(self, value): self.v_hl = (value << 4) | (self.v_hl & 0xf) v = property(__get_v, __set_v) def __get_hl(self): return self.v_hl & 0x0f def __set_hl(self, value): self.v_hl = (self.v_hl & 0xf0) | value hl = property(__get_hl, __set_hl) def __get_flags(self): return (self.off & 0xE000) >> 13 def __set_flags(self, value): self.off = (self.off & ~0xE000) | (value << 13) flags = property(__get_flags, __set_flags) def __get_offset(self): return self.off & ~0xE000 def __set_offset(self, value): self.off = (self.off & 0xE000) | value offset = property(__get_offset, __set_offset) def create_fragments(self, fragment_len=1480): """ Create fragment packets from this IP packet with max fragment_len bytes each. This will set the flags and offset values accordingly (see header field off). fragment_len -- max length of a fragment (IP header + payload) return -- fragment IP packets created from this packet """ if fragment_len % 8 != 0: raise Exception("fragment_len not multipe of 8 bytes: %r" % fragment_len) fragments = [] length_ip_total = len(self.bin()) payload = self.body_bytes length_ip_header = length_ip_total - len(payload) length_payload = length_ip_total - length_ip_header off = 0 while off < length_payload: payload_sub = payload[off:off + fragment_len] ip_frag = IP(id=self.id, p=self.p, src=self.src, dst=self.dst) if length_payload - off > fragment_len: # more fragments follow ip_frag.flags = 0x1 else: # last fragment ip_frag.flags = 0x0 ip_frag.offset = int(off / 8) ip_frag.body_bytes = payload_sub fragments.append(ip_frag) off += fragment_len return fragments # Convenient access for: src[_s], dst[_s] src_s = pypacker.get_property_ip4("src") dst_s = pypacker.get_property_ip4("dst") def _dissect(self, buf): total_header_length = ((buf[0] & 0xf) << 2) options_length = total_header_length - 20 # total IHL - standard IP-len = options length if options_length < 0: # invalid header length: assume no options at all raise Exception("invalid header length: %d" % options_length) elif options_length > 0: # logger.debug("got some IP options: %s" % tl_opts) self._init_triggerlist("opts", buf[20:20 + options_length], self.__parse_opts) self._init_handler(buf[9], buf[total_header_length:]) return total_header_length __IP_OPT_SINGLE = {IP_OPT_EOOL, IP_OPT_NOP} @staticmethod def __parse_opts(buf): """Parse IP options and return them as list.""" optlist = [] i = 0 p = None while i < len(buf): # logger.debug("got IP-option type %s" % buf[i]) if buf[i] in IP.__IP_OPT_SINGLE: p = IPOptSingle(type=buf[i]) i += 1 else: olen = buf[i + 1] # logger.debug("IPOptMulti") p = IPOptMulti(type=buf[i], len=olen, body_bytes=buf[i + 2:i + olen]) # logger.debug("body bytes: %s" % buf[i + 2: i + olen]) i += olen # typefield + lenfield + data-len # logger.debug("IPOptMulti 2") optlist.append(p) return optlist def bin(self, update_auto_fields=True): if update_auto_fields and self._changed(): self._update_bodyhandler_id() if self.len_au_active: self.len = len(self) if self.v_hl_au_active: # Update header length. NOTE: needs to be a multiple of 4 Bytes. # logger.debug("updating: %r" % self._packet) # options length need to be multiple of 4 Bytes self.hl = int(self.header_len / 4) & 0xf if self.sum_au_active: # length changed so we have to recalculate checksum # logger.debug(">>> IP: calculating sum") # reset checksum for recalculation, mark as changed / clear cache self.sum = 0 # logger.debug(">>> IP: bytes for sum: %s" % self.header_bytes) self.sum = in_cksum(self._pack_header()) # logger.debug("IP: new hl: %d / %d" % (self._packet.hdr_len, hdr_len_off)) # logger.debug("new sum: %0X" % self.sum) return pypacker.Packet.bin(self, update_auto_fields=update_auto_fields) def direction(self, other): # logger.debug("checking direction: %s<->%s" % (self, next)) # TODO: handle broadcast if self.src == other.src and self.dst == other.dst: # consider packet to itself: can be DIR_REV return pypacker.Packet.DIR_SAME | pypacker.Packet.DIR_REV elif self.src == other.dst and self.dst == other.src: return pypacker.Packet.DIR_REV else: return pypacker.Packet.DIR_UNKNOWN def reverse_address(self): self.src, self.dst = self.dst, self.src
class NewProtocol(pypacker.Packet): """ New protocols are subclassing Packet class and represent a layer in a multi-layer network Packet like 'NewProtocol | IP | TCP ...'. The whole structure is oriented at the ISO/OSI protocol layers where every layer contains a reference to the next upper layer. As an example this layer 'NewProtocol', when parsing from raw bytes, will have a reference to the next upper layer 'IP' which can be access via '.' like 'newprotoinstance.ip' (access name is the lower case name of the class). Even higher layers can be accessed via 'newprotoinstance.ip.tcp' (when available) or via the '[]' notation like 'newprotoinstance[TCP]'.""" """ The protocol header is basically defined by the static field "__hdr__" (see layer12/ethernet.Ethernet). See code documentation for classes "MetaPacket" and "Packet" in pypacker/pypacker.py for deeper information. """ __hdr__ = ( # Simple constant fields: fixed format, not changing length # marked as type field: defines type of next upper layer, here: IP. See __handler__ ("type", "B", TYPE_VALUE_IP, FIELD_FLAG_IS_TYPEFIELD), ("src", "4s", b"\xff" * 4), ("dst", "4s", b"\xff" * 4), # Simple constant field, marked for auto update, see _update_fields(...). # Stores the full header length inclusive options. ("hlen", "H", 14, FIELD_FLAG_AUTOUPDATE), # Simple constant field, deactivated (see Ethernet -> vlan) # Switching between active/inactive should be avoided because of performance penalty :/ ("idk", "H", None), # Again a simple constant field ("flags", "B", 0), # Dynamic field: bytestring format, *can* change in length, see dns.DNS # Field type should be avoided because of performance penalty :/ ("yolo", None, b"1234"), # TriggerList field: variable length, can contain: raw bytes, key/value-tuples (see HTTP) and Packets (see IP) # Here TriggerList will contain key/value Tuples like (b"\x00", b"1") ("options", None, triggerlist.TriggerList)) # Conveniant access should be enabled using properties eg using pypacker.get_property_xxx(...) src_s = pypacker.get_property_ip4("src") dst_s = pypacker.get_property_ip4("dst") # xxx_s = pypacker.get_property_mac("xxx") # xxx_s = pypacker.get_property_dnsname("xxx") # Setting/getting values smaller then 1 Byte should be enabled using properties (see layer3/ip.IP -> v, hl) def __get_flag_fluxcapacitor(self): return (self.flags & 0x80) >> 15 def __set_flag_fluxcapacitor(self, value): value_shift = (value & 1) << 15 self.flags = (self.flags & ~0x80) | value_shift flag_fluxcapacitor = property(__get_flag_fluxcapacitor, __set_flag_fluxcapacitor) @staticmethod def _parse_options(buf): """ Callback to parse contents for TriggerList-field options, see _dissec(...) -> _init_triggerlist(...). return -- [Option(), ...] """ ret = [] off = 0 while off < len(buf): ret.append(Option(buf[off:off + 2])) off += 2 return ret def _dissect(self, buf): """ _dissect(...) must be overwritten if the header format can change from its original format. This is generally the case when - using TriggerLists (see ip.IP) - simple fields can get deactivated (see ethernet.Ethernet) - using dynamic fields (see dns.DNS) In NewProtocol idk can get deactivated, options is a TriggerList and yolo is a dynamic field so _dissect(...) needs to be defined. """ # Header fields are not yet accessible in _dissect(...) so basic information # (type info, header length, bytes of dynamic content etc) has to be parsed manually. upper_layer_type = buf[ 0] # extract type information of next layer, here it can only be 0x66 but we extract it anyway # logger.debug("Found type: 0x%X" % upper_layer_type) total_header_length = unpack_H(buf[9:11])[0] yolo_len = 4 if upper_layer_type == TYPE_VALUE_IP else 5 # length of yolo is derived from type # logger.debug("Found length: %d, yolo=%d" % (total_header_length, yolo_len)) tl_bts = buf[ 12 + yolo_len: total_header_length] # options are the the end of the header # logger.debug("Bytes for TriggerList: %r" % tl_bts) # self._init_triggerlist(...) should be called to initiate TriggerLists, # otherwise the list will be empty. _parse_options(...) is a callback returning a list # of [raw bytes | key/value tuples | Packets] parsed from tl_bts. self._init_triggerlist("options", tl_bts, NewProtocol._parse_options) # self._init_handler(...) must be called to initiate the handler of the next # upper layer and makes it accessible (eg "ip" in "ethernet" via "ethernet.ip" or ethernet[ip.IP]). # Which handler to be initialized generally depends on the type information (here upper_layer_type) # found in the current layer (see layer12/ethernet.Ethernet -> type). # Here upper_layer_type can become the value 0x66 (defined by __handler__ field) and # as a result ip.IP will be created as upper layer using the bytes given by "buf[total_header_length:]". self._init_handler(upper_layer_type, buf[total_header_length:]) return total_header_length """ Handler can be registered by defining the static dictionary __handler__ where the key is extracted from raw bytes in _dissect(...) and given to _init_handler(...) and the value is the Packet class used to create the next upper layer (here ip.IP). Add the "FIELD_FLAG_IS_TYPEFIELD" to the corresponding type field in __hdr__. """ __handler__ = {TYPE_VALUE_IP: ip.IP} # just 1 possible upper layer def _update_fields(self): """ _update_fields(...) should be overwritten to update fields which depend on the state of the packet like lengths, checksums etc (see layer3/ip.IP -> len, sum) aka auto-update fields. The variable XXX_au_active indicates if the field XXX should be updated (True) or not (see layer3/ip.IP.bin() -> len_au_active). XXX_au_active is available if the field has the flag "FIELD_FLAG_AUTOUPDATE" in __hdr__, default value is True. _update_fields(...) is implicitly called by bin(...). """ if self._changed() and self.hlen_au_active: self.hlen = self.header_len def bin(self, update_auto_fields=True): """ bin(...) should only be overwritten to allow more complex assemblation eg adding padding at the end of all layers instead of the current layer (see ethernet.Ethernet -> padding). The variable update_auto_fields indicates if fields should be updated in general. """ return pypacker.Packet.bin( self, update_auto_fields=update_auto_fields) + b"somepadding" def direction(self, other): """ direction(...) should be overwritten to be able to check directions to an other packet (see layer12/ethernet.Ethernet) """ direction = 0 if self.src == other.src and self.dst == other.dst: direction |= pypacker.Packet.DIR_SAME if self.src == other.dst and self.dst == other.src: direction |= pypacker.Packet.DIR_REV if direction == 0: direction = pypacker.Packet.DIR_UNKNOWN return direction def reverse_address(self): """ reverse_address(...) should be overwritten to be able to reverse source/destination addresses (see ethernet.Ethernet) """ self.src, self.dst = self.dst, self.src
class DHCP(pypacker.Packet): __hdr__ = ( ("op", "B", DHCP_OP_REQUEST), ("hrd", "B", arp.ARP_HRD_ETH), # just like ARP.hrd ("hln", "B", 6), # and ARP.hln ("hops", "B", 0), ("xid", "I", 0xdeadbeef), ("secs", "H", 0), ("flags", "H", 0), ("ciaddr", "4s", b"\x00" * 4), ("yiaddr", "4s", b"\x00" * 4), ("siaddr", "4s", b"\x00" * 4), ("giaddr", "4s", b"\x00" * 4), # MAC + padding ("chaddr", "16s", b"\x00" * 6 + b"\x00" * 10), ("sname", "64s", b"\x00" * 64), ("file", "128s", b"\x00" * 128), ("magic", "I", DHCP_MAGIC), ("opts", None, triggerlist.TriggerList) ) ciaddr_s = pypacker.get_property_ip4("ciaddr") yiaddr_s = pypacker.get_property_ip4("yiaddr") siaddr_s = pypacker.get_property_ip4("siaddr") giaddr_s = pypacker.get_property_ip4("giaddr") def _dissect(self, buf): # logger.debug("DHCP: parsing options, buflen: %d" % len(buf)) self._init_triggerlist("opts", buf[28 + 16 + 64 + 128 + 4:], DHCP.__get_opts) # logger.debug(buf[28+16+64+128+4:]) # logger.debug("amount of options after parsing: %d" % len(self.opts)) return len(buf) @staticmethod def __get_opts(buf): # logger.debug("DHCP: parsing options from: %s" % buf) opts = [] i = 0 while i < len(buf): t = buf[i] p = None # logger.debug("DHCP: adding option type %d" % t) # last option if t in [0, 0xff]: p = DHCPOpt(type=t, len=0) i += 1 else: dlen = buf[i + 1] p = DHCPOpt(type=t, len=dlen, body_bytes=buf[i + 2: i + 2 + dlen]) i += 2 + dlen # logger.debug("new option: %s" % p) opts.append(p) if t == 0xff: if i < len(buf): # padding is part of the options opts.append(Padding(buf[i:])) break return opts
class NewProtocol(pypacker.Packet): """New protocols are subclassing Packet""" """ The protocol header is basically defined by the static field "__hdr__" (see layer12/ethernet.Ethernet). See code documentation for classes "MetaPacket" and "Packet" in pypacker/pypacker.py for deeper information. """ __hdr__ = ( # Simple constant fields (fixed format, not changing size), # marked as type field ("type", "B", 0x12, FIELD_FLAG_IS_TYPEFIELD), ("src", "4s", b"\xff" * 4), ("dst", "4s", b"\xff" * 4), # Simple constant field, deactivatived # Switching between active/inactive should be avoided because of performance penalty :/ ("idk", "H", None), # Simple constant field, marked for auto update (see bin(...)) ("hlen", "H", 14, FIELD_FLAG_AUTOUPDATE), # Simple constant field ("flags", "B", 0), # TriggerList field (variable length, can contain raw bytes, key/value-tuples and Packets) ("options", None, triggerlist.TriggerList), # Dynamic field (bytestring format, *can* change size) # Field type should be avoided because of performance penalty :/ ("yolo", None, b"1234") ) # Conveniant access should be enabled using properties eg using pypacker.get_property_xxx(...) src_s = pypacker.get_property_ip4("src") dst_s = pypacker.get_property_ip4("dst") # xxx_s = pypacker.get_property_mac("xxx") # xxx_s = pypacker.get_property_dnsname("xxx") # Setting/getting values smaller then 1 Byte should be enabled using properties (see layer3/ip.IP -> v, hl) def __get_flag_fluxcapacitor(self): return (self.flags & 0x80) >> 15 def __set_flag_fluxcapacitor(self, value): value_shift = (value & 1) << 15 self.flags = (self.flags & ~0x80) | value_shift flag_fluxcapacitor = property(__get_flag_fluxcapacitor, __set_flag_fluxcapacitor) @staticmethod def _parse_options(buf): """Parse contents for TriggerList-field options""" ret = [] off = 0 while off < len(buf): ret.append(buf[off: off + 2]) off += 2 return ret def _dissect(self, buf): """ _dissect(...) must be overwritten if the header format can change from its original format. This is generally the case when - using TriggerLists - simple fields can get deactivated (see ethernet.Ethernet) - using dynamic fields In NewProtocol idk can get deactivated, options is a TriggerList and yolo is a dynamic field so _dissect(...) needs to be defined. """ # Header fields are not yet accessible in _dissect(...) so basic information # (type info, header length, bytes of dynamic content etc) has to be parsed manually. upper_layer_type = buf[0] total_header_length = unpack_H(buf[9: 11])[0] tl_bts = buf[12: total_header_length - 12] # self._init_triggerlist(...) should be called to initiate TriggerLists. # Otherwise the list will be empty. self._init_triggerlist("options", tl_bts, NewProtocol._parse_options) # self._init_handler(...) can be called to initiate the handler of the next # upper layer. Which handler to be called generally depends on the type information # found in the current layer (see layer12/ethernet.Ethernet -> type) self._init_handler(upper_layer_type, buf[total_header_length:]) """ Handler can be registered by defining the static field dictionary __handler__ where the key is the value given to self._init_handler(...) when dissecting the value is used for creating the upper layer. Add the "FIELD_FLAG_IS_TYPEFIELD" to the corresponding type field in __hdr__. """ __handler__ = {0x66: ip.IP} # just 1 handler, who needs more? def _update_fields(self): """ _update_fields(...) should be overwritten to update fields which depend on the state of the packet like lengths, checksums etc (see layer3/ip.IP -> len, sum) aka auto-update fields. The variable update_auto_fields indicates if any field should be updated in general, XXX_au_active in turn indicates if the field XXX should be updated (True) or not (see layer3/ip.IP.bin() -> len_au_active) in particular. XXX_au_active is available if the field has the flag "FIELD_FLAG_AUTOUPDATE" in __hdr__. """ if update_auto_fields and self._changed() and self.hlen_au_active: self.hlen = self.header_len def bin(self, update_auto_fields=True): """ bin(...) should be overwritten to allow more complex assemblation (eg adding padding at the very end -> see ethernet.Ethernet) """ return pypacker.Packet.bin(self, update_auto_fields=update_auto_fields) + b"somepadding" def direction(self, other): """ direction(...) should be overwritten to be able to check directions to an other packet (see layer12/ethernet.Ethernet) """ direction = 0 if self.src == other.src and self.dst == other.dst: direction |= pypacker.Packet.DIR_SAME if self.src == other.dst and self.dst == other.src: direction |= pypacker.Packet.DIR_REV if direction == 0: direction = pypacker.Packet.DIR_UNKNOWN return direction def reverse_address(self): """ reverse_address(...) should be overwritten to be able to reverse source/destination addresses (see ethernet.Ethernet) """ self.src, self.dst = self.dst, self.src
class IP(pypacker.Packet): __hdr__ = ( ("v_hl", "B", 69), # = 0x45 ("tos", "B", 0), ("len", "H", 20), ("id", "H", 0), ("off", "H", 0), ("ttl", "B", 64), ("p", "B", IP_PROTO_TCP), ("sum", "H", 0), ("src", "4s", b"\x00" * 4), ("dst", "4s", b"\x00" * 4), ("opts", None, triggerlist.TriggerList)) def __get_v(self): return self.v_hl >> 4 def __set_v(self, value): self.v_hl = (value << 4) | (self.v_hl & 0xf) v = property(__get_v, __set_v) def __get_hl(self): return self.v_hl & 0x0f def __set_hl(self, value): self.v_hl = (self.v_hl & 0xf0) | value hl = property(__get_hl, __set_hl) # Convenient access for: src[_s], dst[_s] src_s = pypacker.get_property_ip4("src") dst_s = pypacker.get_property_ip4("dst") def _dissect(self, buf): total_header_length = ((buf[0] & 0xf) << 2) options_length = total_header_length - 20 # total IHL - standard IP-len = options length if options_length < 0: # invalid header length: assume no options at all raise Exception("invalid header length: %d" % options_length) elif options_length > 0: # logger.debug("got some IP options: %s" % tl_opts) self._init_triggerlist("opts", buf[20:20 + options_length], self.__parse_opts) self._init_handler(buf[9], buf[total_header_length:]) return total_header_length __IP_OPT_SINGLE = set([IP_OPT_EOOL, IP_OPT_NOP]) @staticmethod def __parse_opts(buf): """Parse IP options and return them as List.""" optlist = [] i = 0 p = None while i < len(buf): # logger.debug("got IP-option type %s" % buf[i]) if buf[i] in IP.__IP_OPT_SINGLE: p = IPOptSingle(type=buf[i]) i += 1 else: olen = buf[i + 1] # logger.debug("IPOptMulti") p = IPOptMulti(type=buf[i], len=olen, body_bytes=buf[i + 2:i + olen]) # logger.debug("body bytes: %s" % buf[i + 2: i + olen]) i += olen # typefield + lenfield + data-len # logger.debug("IPOptMulti 2") optlist.append(p) return optlist def bin(self, update_auto_fields=True): if update_auto_fields: if self._changed(): self.len = len(self) # length changed so we have to recalculate checksum # logger.debug("updating checksum") # logger.debug(">>> IP: calculating sum") # reset checksum for recalculation, mark as changed / clear cache self.sum = 0 # Update header length. NOTE: needs to be a multiple of 4 Bytes. # logger.debug("updating: %r" % self._packet) # options length need to be multiple of 4 Bytes self._hl = int(self.header_len / 4) & 0xf # logger.debug(">>> IP: bytes for sum: %s" % self.header_bytes) self.sum = in_cksum(self._pack_header()) # logger.debug("IP: new hl: %d / %d" % (self._packet.hdr_len, hdr_len_off)) # logger.debug("new sum: %0X" % self.sum) # logger.debug("new sum: %d" % self.sum) return pypacker.Packet.bin(self, update_auto_fields=update_auto_fields) def direction(self, other): # logger.debug("checking direction: %s<->%s" % (self, next)) # TODO: handle broadcast if self.src == other.src and self.dst == other.dst: # consider packet to itself: can be DIR_REV return pypacker.Packet.DIR_SAME | pypacker.Packet.DIR_REV elif self.src == other.dst and self.dst == other.src: return pypacker.Packet.DIR_REV else: return pypacker.Packet.DIR_UNKNOWN def reverse_address(self): self.src, self.dst = self.dst, self.src