Esempio n. 1
0
class TCP(pypacker.Packet):
    __hdr__ = (
        ("sport", "H", 0xdead),
        ("dport", "H", 0, FIELD_FLAG_AUTOUPDATE | FIELD_FLAG_IS_TYPEFIELD),
        ("seq", "I", 0xdeadbeef),
        ("ack", "I", 0),
        ("off_x2", "B", ((5 << 4) | 0), FIELD_FLAG_AUTOUPDATE),  # 10*4 Byte
        ("flags", "B", TH_SYN),  # acces via (obj.flags & TH_XYZ)
        ("win", "H", TCP_WIN_MAX),
        ("sum", "H", 0, FIELD_FLAG_AUTOUPDATE),
        ("urp", "H", 0),
        ("opts", None, triggerlist.TriggerList))

    # 4 bits | 4 bits
    # offset | reserved
    # offset * 4 = header length
    def __get_off(self):
        return self.off_x2 >> 4

    def __set_off(self, value):
        self.off_x2 = (value << 4) | (self.off_x2 & 0xf)

    off = property(__get_off, __set_off)

    # return real header length based on header info
    def __get_hlen(self):
        return self.off * 4

    # set real header length based on header info (should be n*4)
    def __set_hlen(self, value):
        self.off = int(value / 4)

    hlen = property(__get_hlen, __set_hlen)

    __handler__ = {
        TCP_PROTO_BGP: bgp.BGP,
        TCP_PROTO_TELNET: telnet.Telnet,
        TCP_PROTO_TPKT: tpkt.TPKT,
        TCP_PROTO_PMAP: pmap.Pmap,
        TCP_PROTO_HTTP: http.HTTP,
        TCP_PROTO_SSL: ssl.SSL,
        TCP_PROTO_RTP: rtp.RTP,
        TCP_PROTO_SIP: sip.SIP
    }

    def _update_fields(self):
        # TCP-checksum needs to be updated on one of the following:
        # - this layer itself or any upper layer changed
        # - changes to the IP-pseudoheader
        update = True
        # update header length. NOTE: needs to be a multiple of 4 Bytes.
        # options length need to be multiple of 4 Bytes
        if self._header_changed and self.off_x2_au_active:
            self.off = int(self.header_len / 4) & 0xF

        # we need some IP as lower layer
        if self._lower_layer is None:
            return

        #self._update_upperlayer_id()

        try:
            # changes to IP-layer, don't mind if this isn't IP
            if not self._lower_layer._header_changed:
                # pseudoheader didn't change, further check for changes in layers
                update = self._changed()
            # logger.debug("lower layer found!")
        except AttributeError:
            # assume not an IP packet: we can't calculate the checksum
            # logger.debug("no lower layer found!")
            update = False

        if update and self.sum_au_active:
            # logger.debug(">>> updating checksum")
            self._calc_sum()

    def _dissect(self, buf):
        # update dynamic header parts. buf: 1010???? -clear reserved-> 1010 -> *4
        ol = ((buf[12] >> 4) << 2) - 20  # dataoffset - TCP-standard length

        if ol > 0:
            # parse options, add offset-length to standard-length
            opts_bytes = buf[20:20 + ol]
            self._init_triggerlist("opts", opts_bytes, self._parse_opts)
        elif ol < 0:
            raise Exception("invalid header length")

        ports = [unpack_H(buf[0:2])[0], unpack_H(buf[2:4])[0]]

        try:
            # source or destination port should match
            # logger.debug("TCP handler: %r" % self._id_handlerclass_dct[TCP])
            htype = [x for x in ports
                     if x in self._id_handlerclass_dct[TCP]][0]
            #logger.debug("TCP: trying to set handler, type: %d = %s" %
            #(type, self._id_handlerclass_dct[TCP][type]))
            self._init_handler(htype, buf[20 + ol:])
        except:
            # no type found
            pass
        return 20 + ol

    __TCP_OPT_SINGLE = {TCP_OPT_EOL, TCP_OPT_NOP}

    @staticmethod
    def _parse_opts(buf):
        """Parse TCP options using buf and return them as List."""
        optlist = []
        i = 0

        while i < len(buf):
            # logger.debug("got TCP-option type %s" % buf[i])
            if buf[i] in TCP.__TCP_OPT_SINGLE:
                p = TCPOptSingle(type=buf[i])
                i += 1
            else:
                olen = buf[i + 1]
                # p = TCPOptMulti(type=buf[i], len=olen, body_bytes=buf[i + 2: i + olen])
                p = TCPOptMulti(buf[i:i + olen])
                i += olen  # typefield + lenfield + data-len
            optlist.append(p)
        # logger.debug("tcp: parseopts finished, length: %d" % len(optlist))
        return optlist

    def _calc_sum(self):
        """Recalculate the TCP-checksum. This won't reset changed state."""
        # TCP and underwriting are freaky bitches: we need the IP pseudoheader
        # to calculate their checksum.
        try:
            # we need src/dst for checksum-calculation
            src, dst = self._lower_layer.src, self._lower_layer.dst
            self.sum = 0
            # logger.debug("TCP sum recalc: IP=%d / %s / %s" % (len(src), src, dst))

            tcp_bin = self.header_bytes + self.body_bytes
            # IP-pseudoheader, check if version 4 or 6
            if len(src) == 4:
                s = pack_ipv4_header(src, dst, 6, len(tcp_bin))  # 6 = TCP
            else:
                s = pack_ipv6_header(src, dst, 6, len(tcp_bin))  # 6 = TCP

            # Get checksum of concatenated pseudoheader+TCP packet
            # logger.debug("pseudoheader: %r" % s)
            # logger.debug("tcp_bin: %r" % tcp_bin)
            # assign via non-shadowed variable to trigger re-packing
            self.sum = in_cksum(s + tcp_bin)
            # logger.debug(">>> new checksum: %0X" % self._sum)
        except (AttributeError, struct.error):
            # not an IP packet as lower layer (src, dst not present) or invalid src/dst
            # logger.debug("could not calculate checksum: %r" % e)
            pass

    def direction(self, other):
        direction = 0
        # logger.debug("checking direction: %s<->%s" % (self, other))
        if self.sport == other.sport and self.dport == other.dport:
            direction |= pypacker.Packet.DIR_SAME
        if self.sport == other.dport and self.dport == other.sport:
            direction |= pypacker.Packet.DIR_REV
        if direction == 0:
            direction = pypacker.Packet.DIR_UNKNOWN
        return direction

    def reverse_address(self):
        self.sport, self.dport = self.dport, self.sport

    ra_segments = pypacker.get_ondemand_property("ra_segments", lambda: {})

    def ra_collect(self, pkt_list):
        """
		Collect a TCP segment into ra_segments. Retrieve concatenated
		segments via ra_bin().
		return -- bytes_cnt, [True|False]: amount of bytes added (sum of body bytes)
			and final packet found (RST or FIN)
		"""
        if type(pkt_list) is not list:
            pkt_list = [pkt_list]

        bts_cnt = 0

        for segment in pkt_list:
            if self.direction(segment) != pypacker.Packet.DIR_SAME or len(
                    segment.body_bytes) == 0:
                continue

            seq_store = segment.seq
            # final packet found: connection is going to be terminated
            if (segment.flags & TH_FIN) != 0 or (segment.flags & TH_RST) != 0:
                return 0, True

            if seq_store < self.seq:
                logger.warning("seq of new segment is lower than start")
                seq_store += 0xFFFF

            #logger.debug("adding tcp segment: %r", segment.body_bytes)
            self.ra_segments[seq_store] = segment.body_bytes
            bts_cnt += len(segment.body_bytes)

        return bts_cnt, False

    def ra_bin(self):
        """
		Retrieve sorted and concatenated TCP segments (body bytes of
		TCP segments) a flush internal buffer.
		"""
        self.ra_segments[self.seq] = self.body_bytes
        sorted_list = sorted(self.ra_segments.items(), key=lambda t: t[0])
        bts_lst = [value for key, value in sorted_list]
        return b"".join(bts_lst)
Esempio n. 2
0
class Ethernet(pypacker.Packet):
    __hdr__ = (("dst", "6s", b"\xff" * 6), ("src", "6s", b"\xff" * 6),
               ("vlan", None, triggerlist.TriggerList),
               ("type", "H", ETH_TYPE_IP,
                FIELD_FLAG_AUTOUPDATE | FIELD_FLAG_IS_TYPEFIELD))

    dst_s = pypacker.get_property_mac("dst")
    src_s = pypacker.get_property_mac("src")

    __handler__ = {
        ETH_TYPE_IP: ip.IP,
        ETH_TYPE_ARP: arp.ARP,
        ETH_TYPE_DTP: dtp.DTP,
        ETH_TYPE_IPX: ipx.IPX,
        ETH_TYPE_IP6: ip6.IP6,
        ETH_TYPE_PPOE_DISC: pppoe.PPPoE,
        ETH_TYPE_PPOE_SESS: pppoe.PPPoE,
        ETH_TYPE_PTPv2: ptpv2.PTPv2,
        ETH_TYPE_EFC: flow_control.FlowControl,
        ETH_TYPE_LLDP: lldp.LLDP,
        ETH_TYPE_SP: lacp.LACP,
    }

    def _dissect(self, buf):
        hlen = 14
        # Ethernet formats:
        # RFC 894 (Ethernet II) -> type = -> value >1500
        # 802.[2,3] (LLC format) -> type = length field -> value <=1500, not supported
        eth_type = unpack_H(buf[hlen - 2:hlen])[0]

        # any VLAN tag present? in this case: type field is actually a vlan tag
        if eth_type in VLAN_TAG_START:
            # TODO: use _init_triggerlist()
            if eth_type == ETH_TYPE_8021Q:
                # logger.debug(">>> got vlan tag")
                vlan_tag = Dot1Q(buf[12:16])
                self.vlan.append(vlan_tag)
                hlen += 4
                # get real upper layer type
                eth_type = unpack_H(buf[16:18])[0]
            # 802.1ad: support up to 2 tags (double tagging aka QinQ)
            else:
                # logger.debug(">>> got vlan tag")
                vlan_tag1 = Dot1Q(buf[12:16])
                vlan_tag2 = Dot1Q(buf[16:20])
                self.vlan.extend([vlan_tag1, vlan_tag2])
                hlen += 8
                # get real upper layer type
                eth_type = unpack_H(buf[20:22])[0]

        # logger.debug("eth type is: %d" % eth_type)

        # handle ethernet-padding: remove it but save for later use
        # don't use headers for this because this is a rare situation
        dlen = len(buf) - hlen  # data length [+ padding?]

        # assume padding only present if len(upper_layer.bin()) <= 46
        if dlen <= 46:
            try:
                # this will only work on complete headers: Ethernet + IP + ...
                # handle padding using IPv4, IPv6 etc (min size "eth + ..." = 60 bytes)
                # logger.debug(">>> checking for padding")
                if eth_type == ETH_TYPE_IP:
                    dlen_ip = unpack_H(buf[hlen + 2:hlen +
                                           4])[0]  # real data length

                    if dlen_ip < dlen:
                        # padding found
                        self._padding = buf[hlen + dlen_ip:]
                        # logger.debug("got padding for IPv4: %r" % self._padding)
                        dlen = dlen_ip
                # handle padding using IPv6
                # IPv6 is a piece of sh$§! payloadlength (in header) = exclusive standard header
                # but INCLUSIVE options!
                elif eth_type == ETH_TYPE_IP6:
                    dlen_ip = unpack_H(buf[hlen + 4:hlen +
                                           6])[0]  # real data length
                    # logger.debug("eth.hlen=%d, data length based on header: %d" % (hlen, dlen_ip))

                    if 40 + dlen_ip < dlen:
                        # padding found
                        self._padding = buf[hlen + 40 + dlen_ip:]
                        # logger.debug("got padding for IPv6: %r" % self._padding)
                        dlen = 40 + dlen_ip
                elif eth_type == ETH_TYPE_LLDP:
                    # this is a bit redundant as we re-parse TLV when accessing the LLDP layer
                    dlen_lldp, _ = lldp.count_and_dissect_tlvs(buf[hlen:])
                    self._padding = buf[hlen + dlen_lldp:]
                    dlen = dlen_lldp
                elif eth_type == ETH_TYPE_SP:
                    lacppdu_len = 110
                    self._padding = buf[hlen + lacppdu_len:]
                    dlen = lacppdu_len
            except Exception as ex:
                logger.exception(
                    "could not extract padding info, assuming incomplete ethernet frame: %r",
                    ex)
        # logger.debug("len(buf)=%d, len(upper)=%d" % (len(buf), dlen))
        self._init_handler(eth_type, buf[hlen:hlen + dlen])
        return hlen

    def _update_fields(self):
        self._update_upperlayer_id()

    def bin(self, update_auto_fields=True):
        # padding needs to be placed at the very end
        return pypacker.Packet.bin(
            self, update_auto_fields=update_auto_fields) + self.padding

    def __len__(self):
        return super().__len__() + len(self.padding)

    def direction(self, other):
        # logger.debug("checking direction: %s<->%s" % (self, other))
        if self.dst == other.dst and self.src == other.src:
            # consider packet to itself: can be DIR_REV
            return pypacker.Packet.DIR_SAME | pypacker.Packet.DIR_REV
        if (self.dst == other.src and self.src == other.dst) or\
         (self.dst == b"\xff\xff\xff\xff\xff\xff" and other.dst == self.src):  # broadcast
            return pypacker.Packet.DIR_REV
        return pypacker.Packet.DIR_UNKNOWN

    padding = pypacker.get_ondemand_property("padding", lambda: b"")

    def reverse_address(self):
        self.dst, self.src = self.src, self.dst