Beispiel #1
0
 def __init__(self, parent):
     self.flow_matchmaker = {}
     self.global_flow_lock = threading.RLock()
     self.parent = parent
     self.config = self.parent.config
     if self.config.logging:
         self.log = PcapLogger(self.config.pcap_logdir)
     task = util.ThreadLauncher(self.flow_printer,
                                parent.handle_control_c,
                                respawn=True)
     task.start()
 def __init__(self, parent):
     self.flow_matchmaker = {}
     self.global_flow_lock = threading.RLock()
     self.parent = parent
     self.config = self.parent.config
     if self.config.logging:
         self.log = PcapLogger(self.config.pcap_logdir)
     task = util.ThreadLauncher(self.flow_printer, parent.handle_control_c,
                                respawn=True)
     task.start()
Beispiel #3
0
    def init_logging(self):
        "Setup all logging for the client"
        global log

        rootlogger = logging.getLogger()
        if not self.config.quiet:
            output = logging.StreamHandler(sys.stdout)
            rootlogger.addHandler(output)

        # now remove the temporary stderr output we were using during startup
        log.removeHandler(startup_handler)
        log = rootlogger

        if self.config.logfile not in ("-", None):
            fileout = logging.FileHandler(self.config.logfile)
            rootlogger.addHandler(fileout)
            log.error("Logging events to " + self.config.logfile)

        if self.config.pcap_logdir not in ("-", None):
            self.pcap_logger = PcapLogger(self.config.pcap_logdir)
        else:
            self.pcap_logger = None
Beispiel #4
0
class Matchmaker:
    """
    The Matchmaker is responsible for determining when a flow reported by
    Alice to Bob and a flow reported by Bob to Alice are in fact the same
    flow.  In the absence of NATs, port forwarding, transparent proxies and
    soforth this would be trivial.  But it isn't.
    """
    def __init__(self, parent):
        self.flow_matchmaker = {}
        self.global_flow_lock = threading.RLock()
        self.parent = parent
        self.config = self.parent.config
        if self.config.logging:
            self.log = PcapLogger(self.config.pcap_logdir)
        task = util.ThreadLauncher(self.flow_printer,
                                   parent.handle_control_c,
                                   respawn=True)
        task.start()

    def add_flow(self, link, id, f_tuple, m_tuple):
        """ 
        We have a newly reported flow from a client.  Ensure that the
        proper data structures are in place for it.  In this instance we
        assume that either end could be firewalled.  
        """

        # This is a bit tricky.  First, we adopt the convention that Alice is
        # always the sender for a flow, and Bob the receiver (so for a TCP
        # session, each end is Alice in one direction and Bob in the other).

        # Next, observe that both Alice and Bob will send "active-flows"
        # messages adding the flow.  Those might arrive in either order.
        # Furthermore, Bob might send an "active-flows", then a whole lot of
        # "recv"s, long before Alice sends her "active-flow" -- so whichever end
        # sends first needs to trigger the instantiation of the Flows and the
        # Reconciliator.

        # XXX There are some nasty special cases we don't currently handle here:
        # 1. The first packet in a flow is mangled in transit, so the
        # opening_hashes don't match.  The port numbers will sometimes tip us of
        # to this
        # 2. The first packet in a flow does not arrive first.  This shouldn't
        # be possible for TCP, but it may well happen with other protocols.

        f = f_tuple
        self.global_flow_lock.acquire()
        try:
            if m_tuple not in self.flow_matchmaker:

                errlog.info("Creating flow: %s" % ` print_flow_tuple(f) `)
                rec = Reconciliator.Reconciliator(f, m_tuple)
                rec.add_link(link, id, f)  # it'll figure out which side we are

                self.flow_matchmaker[m_tuple] = rec
                errlog.debug("Matchmaker is now %s" % ` self.flow_matchmaker `)
            else:
                rec = self.flow_matchmaker[m_tuple]
                if rec.add_link(link, id, f):
                    # we have two sides to this flow now
                    if self.config.logging:
                        self.log.new_flow(print_flow_tuple(f_tuple), rec.id)
        finally:
            self.global_flow_lock.release()

        # now register the flow in the link itself
        link.flow_lock.acquire()
        try:
            link.flow_table[id] = (f_tuple, rec)
        finally:
            link.flow_lock.release()

    def delete_flow(self, link, alice_id):
        "Remove a flow from the link and global flow tables."
        link.flow_lock.acquire()
        try:
            try:
                entry = link.flow_table[alice_id]
                if entry == None:
                    # We should never have had this flow anyway
                    del link.flow_table[alice_id]
                    return None
                f_tuple, rec = entry
                del link.flow_table[alice_id]
            except KeyError:
                log.warn("Attempted delete_flow %d but it's already gone" %
                         alice_id)
                return None
        finally:
            link.flow_lock.release()
        self.remove_flow_from_matchmaker(rec)

    def remove_flow_from_matchmaker(self, rec):
        self.global_flow_lock.acquire()
        # The other link will still have a reference to the Flows and
        # Reconciliator, so it won't matter if its gone from the flow_matchmaker
        try:
            try:
                # XXX research question: this table entry contains Flows and a
                # Reconciliator that references those flows.  Do we need to do
                # more to avoid garbage collection difficulties?
                del self.flow_matchmaker[rec.m_tuple]
            except KeyError:
                pass
        finally:
            self.global_flow_lock.release()

    def judgement_day(self):
        """
      Used for testing: reconcile all packets now, regardless of when the
      latest information from the clients arrived.
      """
        self.global_flow_lock.acquire()
        errlog.warn("Entering judgement day")
        try:
            for rec in self.flow_matchmaker.values():
                rec.final_judgement()
        finally:
            self.global_flow_lock.release()

    def print_global_flow_table(self):
        "(An obsolete name.)"
        return self.prettyprint_flows()

    def flow_printer(self, print_mms=True):
        "Run this in a thread.  Print the global flow table from time to time"
        while True:
            time.sleep(21)
            self.prettyprint_flows()

    def prettyprint_flows(self, print_mms=True):
        """
      Pretty print the global flow tables.  Return a tuple for testing:
      (total flow pairs, total reconciled packets, total leftovers)
      """
        # XXX this function is too large and ugly and too complicated.  Fix it.

        errlog.info(
            "\nCURRENT FLOW TABLE:                            okay  drop mod/frg pend t/rx prot"
        )
        self.global_flow_lock.acquire()
        try:
            flows = {}
            for rec in self.flow_matchmaker.values():
                if rec.ready:
                    flows[rec.flow] = rec
            plist = []  # server side list of summaries
            notifications = {}  # maps link -> list of flow summaries
            total_leftovers = 0
            total_okay = 0
            total_dropped = 0
            total_forged = 0
            n = 0
            for mm, rec in self.flow_matchmaker.items():
                if rec.flow in flows:
                    f = rec.flow
                    reclist = [(f, rec)]  # list will be of length 1 or 2
                    mirror = (f[2], f[3], f[0], f[1], f[4])
                    if mirror in flows:
                        reclist.append((mirror, flows[mirror]))
                    else:
                        errlog.info("No mirror for %s", ` mm `)

                    for flow, rec in reclist:
                        rec.lock.acquire()
                        try:
                            total_leftovers += sum(rec.leftovers())
                            total_forged += rec.forged_packets
                            total_dropped += rec.dropped_packets
                            total_okay += rec.okay_packets
                            try:
                                assert rec.ready  # debugging weird errors
                            except:
                                continue
                            summary = rec.prettyprint()
                            plist.append(summary)
                            for link, id in rec.src_links + rec.dest_links:
                                notifications.setdefault(link,
                                                         []).append(summary)
                            del flows[flow]
                        finally:
                            rec.lock.release()
                    n += 1
            for summary in plist:
                errlog.info(summary)
            if self.config.send_flow_status_updates:
                for link, summaries in notifications.items():
                    # don't send an identical flow table to a client repeatedly
                    msg = "\n".join(summaries)
                    if link.last_status != msg:
                        link.last_status = msg
                        link.send_other_message("flow-status",
                                                [table_header + msg])
        finally:
            self.global_flow_lock.release()
        return (n, total_okay, total_leftovers, total_forged, total_dropped)
class Matchmaker:
    """
    The Matchmaker is responsible for determining when a flow reported by
    Alice to Bob and a flow reported by Bob to Alice are in fact the same
    flow.  In the absence of NATs, port forwarding, transparent proxies and
    soforth this would be trivial.  But it isn't.
    """

    def __init__(self, parent):
        self.flow_matchmaker = {}
        self.global_flow_lock = threading.RLock()
        self.parent = parent
        self.config = self.parent.config
        if self.config.logging:
            self.log = PcapLogger(self.config.pcap_logdir)
        task = util.ThreadLauncher(self.flow_printer, parent.handle_control_c,
                                   respawn=True)
        task.start()


    def add_flow(self, link, id, f_tuple, m_tuple):
        """ 
        We have a newly reported flow from a client.  Ensure that the
        proper data structures are in place for it.  In this instance we
        assume that either end could be firewalled.  
        """

        # This is a bit tricky.  First, we adopt the convention that Alice is
        # always the sender for a flow, and Bob the receiver (so for a TCP
        # session, each end is Alice in one direction and Bob in the other).

        # Next, observe that both Alice and Bob will send "active-flows"
        # messages adding the flow.  Those might arrive in either order.
        # Furthermore, Bob might send an "active-flows", then a whole lot of
        # "recv"s, long before Alice sends her "active-flow" -- so whichever end
        # sends first needs to trigger the instantiation of the Flows and the
        # Reconciliator.  
        
        # XXX There are some nasty special cases we don't currently handle here:
        # 1. The first packet in a flow is mangled in transit, so the
        # opening_hashes don't match.  The port numbers will sometimes tip us of
        # to this
        # 2. The first packet in a flow does not arrive first.  This shouldn't
        # be possible for TCP, but it may well happen with other protocols.

        f = f_tuple
        self.global_flow_lock.acquire()
        try:
          if m_tuple not in self.flow_matchmaker:
     
            errlog.info("Creating flow: %s" % `print_flow_tuple(f)`)
            rec = Reconciliator.Reconciliator(f,m_tuple)
            rec.add_link(link, id, f)        # it'll figure out which side we are

            self.flow_matchmaker[m_tuple] = rec
            errlog.debug("Matchmaker is now %s" % `self.flow_matchmaker`)
          else:
            rec = self.flow_matchmaker[m_tuple]
            if rec.add_link(link, id, f):
              # we have two sides to this flow now
              if self.config.logging: 
                self.log.new_flow(print_flow_tuple(f_tuple), rec.id)
        finally:
          self.global_flow_lock.release()
        
        # now register the flow in the link itself
        link.flow_lock.acquire()
        try:
          link.flow_table[id] = (f_tuple, rec)
        finally:
          link.flow_lock.release()


    def delete_flow(self, link, alice_id):
        "Remove a flow from the link and global flow tables."
        link.flow_lock.acquire()
        try:
            try:
                entry = link.flow_table[alice_id]
                if entry == None:
                    # We should never have had this flow anyway
                    del link.flow_table[alice_id]
                    return None
                f_tuple, rec = entry
                del link.flow_table[alice_id]
            except KeyError:
                log.warn("Attempted delete_flow %d but it's already gone"%alice_id)
                return None
        finally:
            link.flow_lock.release()
        self.remove_flow_from_matchmaker(rec)


    def remove_flow_from_matchmaker(self, rec):
        self.global_flow_lock.acquire()
        # The other link will still have a reference to the Flows and
        # Reconciliator, so it won't matter if its gone from the flow_matchmaker
        try:
            try:
                # XXX research question: this table entry contains Flows and a
                # Reconciliator that references those flows.  Do we need to do
                # more to avoid garbage collection difficulties?
                del self.flow_matchmaker[rec.m_tuple]
            except KeyError:
                pass
        finally:
            self.global_flow_lock.release()


    def judgement_day(self):
      """
      Used for testing: reconcile all packets now, regardless of when the
      latest information from the clients arrived.
      """
      self.global_flow_lock.acquire()
      errlog.warn("Entering judgement day")
      try:
        for rec in self.flow_matchmaker.values():
          rec.final_judgement()
      finally:
        self.global_flow_lock.release()

    def print_global_flow_table(self):
      "(An obsolete name.)"
      return self.prettyprint_flows()

    def flow_printer(self, print_mms=True):
      "Run this in a thread.  Print the global flow table from time to time"
      while True:
        time.sleep(21)
        self.prettyprint_flows()

    def prettyprint_flows(self, print_mms=True):
      """
      Pretty print the global flow tables.  Return a tuple for testing:
      (total flow pairs, total reconciled packets, total leftovers)
      """
      # XXX this function is too large and ugly and too complicated.  Fix it.

      errlog.info("\nCURRENT FLOW TABLE:                            okay  drop mod/frg pend t/rx prot")
      self.global_flow_lock.acquire()
      try:
        flows = {}
        for rec in self.flow_matchmaker.values():
          if rec.ready:
            flows[rec.flow] = rec
        plist = []         # server side list of summaries
        notifications = {} # maps link -> list of flow summaries
        total_leftovers = 0
        total_okay = 0
        total_dropped = 0
        total_forged = 0
        n = 0
        for mm, rec in self.flow_matchmaker.items():
          if rec.flow in flows:
            f = rec.flow
            reclist = [(f, rec)]  # list will be of length 1 or 2
            mirror = (f[2], f[3], f[0], f[1], f[4])
            if mirror in flows: 
              reclist.append((mirror, flows[mirror]))
            else:
              errlog.info("No mirror for %s", `mm`)

            for flow,rec in reclist:
              rec.lock.acquire()
              try:
                total_leftovers += sum(rec.leftovers())
                total_forged += rec.forged_packets
                total_dropped += rec.dropped_packets
                total_okay += rec.okay_packets
                try:
                  assert rec.ready    # debugging weird errors
                except:
                  continue
                summary = rec.prettyprint()
                plist.append(summary)
                for link,id in rec.src_links + rec.dest_links:
                  notifications.setdefault(link, []).append(summary)
                del flows[flow]
              finally:
                rec.lock.release()
            n += 1
        for summary in plist:
          errlog.info(summary)
        if self.config.send_flow_status_updates:
          for link, summaries in notifications.items():
            # don't send an identical flow table to a client repeatedly
            msg = "\n".join(summaries)
            if link.last_status != msg:
              link.last_status = msg
              link.send_other_message("flow-status", [table_header + msg])
      finally:
        self.global_flow_lock.release()
      return (n, total_okay, total_leftovers, total_forged, total_dropped)