<commit_before>#!/usr/bin/env python
# Nom nom nom nom

# TODO: future feature: colored cli prompts to make packets vs. crashes
#       vs. whatever easy to distinguish

# TODO: rather than just prompting "Continue to next round? [Yn]", allow
#       the user to examine the state of the network interactively (i.e.,
#       provide them with the normal POX cli + the simulated events

import pox.openflow.libopenflow_01 as of
from pox.lib.revent import EventMixin
from debugger_entities import Link, Host

from traffic_generator import TrafficGenerator
from invariant_checker import InvariantChecker

import sys
import threading
import signal
import subprocess
import socket
import time
import random
import os
import logging
import pickle

from sts.console import msg

log = logging.getLogger("debugger")

class FuzzTester (EventMixin):
  """
  This is part of a testing framework for controller applications. It
  acts as a replacement for pox.topology.

  Given a set of event handlers (registered by a controller application),
  it will inject intelligently chosen mock events (and observe
  their responses?)
  """
  def __init__(self, fuzzer_params="fuzzer_params.cfg", interactive=True,
               check_interval=35, trace_interval=10, random_seed=0.0,
               delay=0.1, dataplane_trace=None, control_socket=None):
    self.interactive = interactive
    self.check_interval = check_interval
    self.trace_interval = trace_interval
    # Format of trace file is a pickled array of DataplaneEvent objects
    self.dataplane_trace = None
    if dataplane_trace:
      self.dataplane_trace = pickle.load(file(dataplane_trace))
    self.running = False
    self.panel = None
    self.switch_impls = []

    self.delay = delay

    self._load_fuzzer_params(fuzzer_params)

    # Logical time (round #) for the simulation execution
    self.logical_time = 0

    # Metatdata for simulated failures
    # sts.debugger_entities.Link objects
    self.cut_links = set()
    # SwitchOutDpEvent objects
    self.dropped_dp_events = []
    # SwitchImpl objects
    self.failed_switches = set()
    # topology.Controller objects
    self.failed_controllers = set()
    # ?
    self.cancelled_timeouts = set()

    # Statistics to print on exit
    self.packets_sent = 0

    # Make execution deterministic to allow the user to easily replay
    self.seed = random_seed
    self.random = random.Random(self.seed)
    self.traffic_generator = TrafficGenerator(self.random)
    self.invariant_checker = InvariantChecker(control_socket)

    # TODO: future feature: log all events, and allow user to (interactively)
    # replay the execution
    # self.replay_logger = ReplayLogger()
    # Write the seed out to the replay log as the first 4 bytes

    # TODO: future feature: allow the user to interactively choose the order
    # events occur for each round, whether to delay, drop packets, fail nodes,
    # etc.
    # self.failure_lvl = [
    #   NOTHING,    # Everything is handled by the random number generator
    #   CRASH,      # The user only controls node crashes and restarts
    #   DROP,       # The user also controls message dropping
    #   DELAY,      # The user also controls message delays
    #   EVERYTHING  # The user controls everything, including message ordering
    # ]

    # TODO: need a mechanism for signaling  when the distributed controller handshake has completed

  def _load_fuzzer_params(self, fuzzer_params):
    if os.path.exists(fuzzer_params):
      # TODO: more pythonic way to read lines (currently on a plane...)
      # TODO: even better: there is probably a library for parsing config files
      for line in file(fuzzer_params).read().splitlines():
        if line == "[fuzzer]":
          # TODO: handle more directives other than [fuzzer]
          continue
        (kw, eq, val) = line.split()
        val = float(val)
        setattr(self, kw, val)
    else:
      # TODO: default values in case fuzzer_config is not present / missing directives
      raise IOError("Could not find logging config file: %s" % fuzzer_params)
    
  def type_check_dataplane_trace(self):
    if self.dataplane_trace is not None:
      for dp_event in self.dataplane_trace:
        if dp_event.interface not in self.interface2host:
          raise RuntimeError("Dataplane trace does not type check (%s)" % str(dp_event.interface))

  def simulate(self, panel, switch_impls, network_links, hosts, access_links, steps=None):
    """
    Start the fuzzer loop!
    """
    log.debug("Starting fuzz loop")
    self.panel = panel
    self.switch_impls = set(switch_impls)
    self.dataplane_links = set(network_links)
    self.hosts = hosts
    self.interface2host = {}
    for host in hosts:
      for interface in host.interfaces:
        self.interface2host[interface] = host
    self.access_links = set(access_links)
    self.type_check_dataplane_trace()
    self.loop(steps)

  def loop(self, steps=None):
    self.running = True
    end_time = self.logical_time + steps if steps else sys.maxint
    while self.running and self.logical_time < end_time:
      self.logical_time += 1
      self.trigger_events()
      msg.event("Round %d completed." % self.logical_time)

      if self.interactive:
        # TODO: print out the state of the network at each timestep? Take a
        # verbose flag..
        self.invariant_check_prompt()
        self.dataplane_trace_prompt()
        answer = msg.raw_input('Continue to next round? [Yn]').strip()
        if answer != '' and answer.lower() != 'y':
          self.stop()
          break
      else: # not self.interactive
        if (self.logical_time % self.check_interval) == 0:
          # Time to run correspondence!
          # spawn a thread for running correspondence. Make sure the controller doesn't 
          # think we've gone idle though: send OFP_ECHO_REQUESTS every few seconds
          # TODO: this is a HACK
          def do_correspondence():
            any_policy_violations = self.invariant_checker.check_correspondence(self.live_switches, self.live_links, self.access_links)
            if any_policy_violations:
              msg.fail("There were policy-violations!")
            else:
              msg.interactive("No policy-violations!")
          thread = threading.Thread(target=do_correspondence)
          thread.start()
          while thread.isAlive():
            for switch in self.live_switches:
              # connection -> deferred io worker -> io worker
              switch.send(of.ofp_echo_request().pack())
            thread.join(2.0)
     
        if self.dataplane_trace and (self.logical_time % self.trace_interval) == 0:
          self.inject_trace_event()
          
        time.sleep(self.delay)

  def stop(self):
    self.running = False
    
  def invariant_check_prompt(self):
    answer = msg.raw_input('Check Invariants? [Ny]')
    if answer != '' and answer.lower() != 'n':
      msg.interactive("Which one?")
      msg.interactive("  'l' - loops")
      msg.interactive("  'b' - blackholes")
      msg.interactive("  'r' - routing consistency")
      msg.interactive("  'c' - connectivity")
      msg.interactive("  'o' - omega")
      answer = msg.raw_input("  ")
      result = None
      if answer.lower() == 'l':
        result = self.invariant_checker.check_loops()
      elif answer.lower() == 'b':
        result = self.invariant_checker.check_blackholes()
      elif answer.lower() == 'r':
        result = self.invariant_checker.check_routing_consistency()
      elif answer.lower() == 'c':
        result = self.invariant_checker.check_connectivity()
      elif answer.lower() == 'o':
        result = self.invariant_checker.check_correspondence(self.live_switches,
                                                             self.live_links,
                                                             self.access_links)
      else:
        log.warn("Unknown input...")

      if result is None:
        return
      else:
        msg.interactive("Result: %s" % str(result))
        
  def dataplane_trace_prompt(self):
    if self.dataplane_trace:
      while True:
        answer = msg.raw_input('Feed in next dataplane event? [Ny]')
        if answer != '' and answer.lower() != 'n':
          self.inject_trace_event()<commit_msg>adding better prompt for interactive mode<commit_after>#!/usr/bin/env python
# Nom nom nom nom

# TODO: future feature: colored cli prompts to make packets vs. crashes
#       vs. whatever easy to distinguish

# TODO: rather than just prompting "Continue to next round? [Yn]", allow
#       the user to examine the state of the network interactively (i.e.,
#       provide them with the normal POX cli + the simulated events

import pox.openflow.libopenflow_01 as of
from pox.lib.revent import EventMixin
from debugger_entities import Link, Host

from traffic_generator import TrafficGenerator
from invariant_checker import InvariantChecker

import sys
import threading
import signal
import subprocess
import socket
import time
import random
import os
import logging
import pickle

from sts.console import msg

log = logging.getLogger("debugger")

class FuzzTester (EventMixin):
  """
  This is part of a testing framework for controller applications. It
  acts as a replacement for pox.topology.

  Given a set of event handlers (registered by a controller application),
  it will inject intelligently chosen mock events (and observe
  their responses?)
  """
  def __init__(self, fuzzer_params="fuzzer_params.cfg", interactive=True,
               check_interval=35, trace_interval=10, random_seed=0.0,
               delay=0.1, dataplane_trace=None, control_socket=None):
    self.interactive = interactive
    self.check_interval = check_interval
    self.trace_interval = trace_interval
    # Format of trace file is a pickled array of DataplaneEvent objects
    self.dataplane_trace = None
    if dataplane_trace:
      self.dataplane_trace = pickle.load(file(dataplane_trace))
    self.running = False
    self.panel = None
    self.switch_impls = []

    self.delay = delay

    self._load_fuzzer_params(fuzzer_params)

    # Logical time (round #) for the simulation execution
    self.logical_time = 0

    # Metatdata for simulated failures
    # sts.debugger_entities.Link objects
    self.cut_links = set()
    # SwitchOutDpEvent objects
    self.dropped_dp_events = []
    # SwitchImpl objects
    self.failed_switches = set()
    # topology.Controller objects
    self.failed_controllers = set()
    # ?
    self.cancelled_timeouts = set()

    # Statistics to print on exit
    self.packets_sent = 0

    # Make execution deterministic to allow the user to easily replay
    self.seed = random_seed
    self.random = random.Random(self.seed)
    self.traffic_generator = TrafficGenerator(self.random)
    self.invariant_checker = InvariantChecker(control_socket)

    # TODO: future feature: log all events, and allow user to (interactively)
    # replay the execution
    # self.replay_logger = ReplayLogger()
    # Write the seed out to the replay log as the first 4 bytes

    # TODO: future feature: allow the user to interactively choose the order
    # events occur for each round, whether to delay, drop packets, fail nodes,
    # etc.
    # self.failure_lvl = [
    #   NOTHING,    # Everything is handled by the random number generator
    #   CRASH,      # The user only controls node crashes and restarts
    #   DROP,       # The user also controls message dropping
    #   DELAY,      # The user also controls message delays
    #   EVERYTHING  # The user controls everything, including message ordering
    # ]

    # TODO: need a mechanism for signaling  when the distributed controller handshake has completed

  def _load_fuzzer_params(self, fuzzer_params):
    if os.path.exists(fuzzer_params):
      # TODO: more pythonic way to read lines (currently on a plane...)
      # TODO: even better: there is probably a library for parsing config files
      for line in file(fuzzer_params).read().splitlines():
        if line == "[fuzzer]":
          # TODO: handle more directives other than [fuzzer]
          continue
        (kw, eq, val) = line.split()
        val = float(val)
        setattr(self, kw, val)
    else:
      # TODO: default values in case fuzzer_config is not present / missing directives
      raise IOError("Could not find logging config file: %s" % fuzzer_params)
    
  def type_check_dataplane_trace(self):
    if self.dataplane_trace is not None:
      for dp_event in self.dataplane_trace:
        if dp_event.interface not in self.interface2host:
          raise RuntimeError("Dataplane trace does not type check (%s)" % str(dp_event.interface))

  def simulate(self, panel, switch_impls, network_links, hosts, access_links, steps=None):
    """
    Start the fuzzer loop!
    """
    log.debug("Starting fuzz loop")
    self.panel = panel
    self.switch_impls = set(switch_impls)
    self.dataplane_links = set(network_links)
    self.hosts = hosts
    self.interface2host = {}
    for host in hosts:
      for interface in host.interfaces:
        self.interface2host[interface] = host
    self.access_links = set(access_links)
    self.type_check_dataplane_trace()
    self.loop(steps)

  def loop(self, steps=None):
    self.running = True
    end_time = self.logical_time + steps if steps else sys.maxint
    while self.running and self.logical_time < end_time:
      self.logical_time += 1
      self.trigger_events()
      msg.event("Round %d completed." % self.logical_time)

      if self.interactive:
        # TODO: print out the state of the network at each timestep? Take a
        # verbose flag..
        self.invariant_check_prompt()
        self.dataplane_trace_prompt()
        answer = msg.raw_input('Continue to next round? [Yn]').strip()
        if answer != '' and answer.lower() != 'y':
          self.stop()
          break
      else: # not self.interactive
        if (self.logical_time % self.check_interval) == 0:
          # Time to run correspondence!
          # spawn a thread for running correspondence. Make sure the controller doesn't 
          # think we've gone idle though: send OFP_ECHO_REQUESTS every few seconds
          # TODO: this is a HACK
          def do_correspondence():
            any_policy_violations = self.invariant_checker.check_correspondence(self.live_switches, self.live_links, self.access_links)
            if any_policy_violations:
              msg.fail("There were policy-violations!")
            else:
              msg.interactive("No policy-violations!")
          thread = threading.Thread(target=do_correspondence)
          thread.start()
          while thread.isAlive():
            for switch in self.live_switches:
              # connection -> deferred io worker -> io worker
              switch.send(of.ofp_echo_request().pack())
            thread.join(2.0)
     
        if self.dataplane_trace and (self.logical_time % self.trace_interval) == 0:
          self.inject_trace_event()
          
        time.sleep(self.delay)

  def stop(self):
    self.running = False
    
  def invariant_check_prompt(self):
    answer = msg.raw_input('Check Invariants? [Ny]')
    if answer != '' and answer.lower() != 'n':
      msg.interactive("Which one?")
      msg.interactive("  'l' - loops")
      msg.interactive("  'b' - blackholes")
      msg.interactive("  'r' - routing consistency")
      msg.interactive("  'c' - connectivity")
      msg.interactive("  'o' - omega")
      answer = msg.raw_input("> ")
      result = None
      if answer.lower() == 'l':
        result = self.invariant_checker.check_loops()
      elif answer.lower() == 'b':
        result = self.invariant_checker.check_blackholes()
      elif answer.lower() == 'r':
        result = self.invariant_checker.check_routing_consistency()
      elif answer.lower() == 'c':
        result = self.invariant_checker.check_connectivity()
      elif answer.lower() == 'o':
        result = self.invariant_checker.check_correspondence(self.live_switches,
                                                             self.live_links,
                                                             self.access_links)
      else:
        log.warn("Unknown input...")

      if result is None:
        return
      else:
        msg.interactive("Result: %s" % str(result))
        
  def dataplane_trace_prompt(self):
    if self.dataplane_trace:
      while True:
        answer = msg.raw_input('Feed in next dataplane event? [Ny]')
        if answer != '' and answer.lower() != 'n':
          self.inject_trace_event()
{'fraction_non_alphanumeric': 0.08800144560896278, 'fraction_numerical': 0.002529815684857246, 'mean_word_length': 2.7085101630556174, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1017392', 'n_tokens_mistral': 4866, 'n_tokens_neox': 4679, 'n_words': 1422}