Ejemplo n.º 1
0
  def __init__(self, *args, **kwargs):
    super(CoscinApp, self).__init__(*args, **kwargs)

    # The nib is the universal variable containing network configuration and 
    # learned state (so far)
    nib = NetworkInformationBase()
    self.nib = nib

    config_file = os.getenv("COSCIN_CFG_FILE", "coscin_gates_testbed.json")
    nib.load_config(config_file)

    hostname = socket.gethostname()
    on_switch = self.nib.switch_for_controller_host(hostname)
    if on_switch == None:
      self.logger.error("The hostname "+hostname+" is not present in a controller_hosts attribute for the switch in "+config_file)
      sys.exit(1)
    zookeeper_for_switch = self.nib.zookeeper_for_switch(on_switch)
    if zookeeper_for_switch == "":
      self.mc = None
    else:
      self.mc = MultipleControllers(self.logger, hostname, zookeeper_for_switch)
      self.heartbeat_monitor_started = False

    # Register all handlers
    self.l2_learning_switch_handler = L2LearningSwitchHandler(nib, self.logger)
    self.cross_campus_handler = CrossCampusHandler(nib, self.logger)
    self.arp_handler = ArpHandler(nib, self.logger)
    self.path_selection_handler = PathSelectionHandler(nib, self.logger)
Ejemplo n.º 2
0
  def __init__(self, config_file='laptop_demo_network.json'):
    frenetic.App.__init__(self) 
    nib = NetworkInformationBase()
    self.nib = nib
    nib.load_config(config_file)

    self.arp_handler = ArpHandler(self, nib)
    self.intranet_handler = IntranetHandler(self, nib)
    self.cross_campus_handler = CrossCampusHandler(self, nib)
Ejemplo n.º 3
0
class CoscinApp(app_manager.RyuApp):
  OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

  # This is the maximum number of seconds between probes from the switch.  On HP switches, it's listed
  # as the Backoff interval
  MAXIMUM_HEARTBEAT_INTERVAL = 20

  def __init__(self, *args, **kwargs):
    super(CoscinApp, self).__init__(*args, **kwargs)

    # The nib is the universal variable containing network configuration and 
    # learned state (so far)
    nib = NetworkInformationBase()
    self.nib = nib

    config_file = os.getenv("COSCIN_CFG_FILE", "coscin_gates_testbed.json")
    nib.load_config(config_file)

    hostname = socket.gethostname()
    on_switch = self.nib.switch_for_controller_host(hostname)
    if on_switch == None:
      self.logger.error("The hostname "+hostname+" is not present in a controller_hosts attribute for the switch in "+config_file)
      sys.exit(1)
    zookeeper_for_switch = self.nib.zookeeper_for_switch(on_switch)
    if zookeeper_for_switch == "":
      self.mc = None
    else:
      self.mc = MultipleControllers(self.logger, hostname, zookeeper_for_switch)
      self.heartbeat_monitor_started = False

    # Register all handlers
    self.l2_learning_switch_handler = L2LearningSwitchHandler(nib, self.logger)
    self.cross_campus_handler = CrossCampusHandler(nib, self.logger)
    self.arp_handler = ArpHandler(nib, self.logger)
    self.path_selection_handler = PathSelectionHandler(nib, self.logger)

  # The heartbeat timer is started on switch startup.  It ensures that a heartbeat occurs every
  # 10 seconds or so by default.  
  def heartbeat_monitor(self, _):
    while True:
      # If we don't currently hold the lock as a master, just sleep until we do.  
      if self.mc.holds_lock():
        secs_ago = time.time() - self.last_heartbeat
        # If it has stopped, then we relinquish the lock in Zookeeper so the backup controller can be promoted
        # At this point, the controller is in limbo as far as Zookeeper is concerned.  When the Switch Up event
        # happens, it will become a backup controller.  Note: we don't actually send a role_request to the switch
        # to demote it because ... well, we can't contact the switch!  The promotion to master of the other 
        # controller will ensure the demotion of this one takes place. 
        if secs_ago > (2.0 * self.MAXIMUM_HEARTBEAT_INTERVAL):
          self.logger.error("Lost connection with the switch.  Demoting to backup controller.")
          self.mc.release_lock()
          self.nib.clear()
      time.sleep(self.MAXIMUM_HEARTBEAT_INTERVAL)

  @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
  def handle_switch_up(self, ev):
    dp = ev.msg.datapath
    ofp_parser = dp.ofproto_parser
    ofp = dp.ofproto
    self.logger.info("Switch "+str(dp.id)+" says hello.")
    switch = self.nib.save_switch(dp)
    self.logger.info("Connected to Switch: "+self.nib.switch_description(dp))

    if self.mc != None:
      # This will block until the controller actually becomes a primary
      self.mc.handle_datapath(ev)

      # Start background thread to monitor switch-to-controller heartbeat
      self.last_heartbeat = time.time()
      if not self.heartbeat_monitor_started:
        thread.start_new_thread( self.heartbeat_monitor, (self, ) )
      self.heartbeat_monitor_started = True

    OpenflowUtils.delete_all_rules(dp)
    OpenflowUtils.send_table_miss_config(dp)

    self.l2_learning_switch_handler.install_fixed_rules(dp)
    self.cross_campus_handler.install_fixed_rules(dp)
    self.arp_handler.install_fixed_rules(dp)
    self.path_selection_handler.install_fixed_rules(dp)

  @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
  def handle_packet_in(self, ev):
    # The HP considers a successful Packet In as a probe, so we reset the heartbeat here as well
    self.last_heartbeat = time.time()

    msg = ev.msg
    self.logger.debug("Packet In")
    self.l2_learning_switch_handler.packet_in(msg)
    self.cross_campus_handler.packet_in(msg)
    self.arp_handler.packet_in(msg)
    self.path_selection_handler.packet_in(msg)

  @set_ev_cls(ofp_event.EventOFPErrorMsg, [CONFIG_DISPATCHER, MAIN_DISPATCHER])
  def error_msg_handler(self, ev):
    msg = ev.msg
    self.logger.error('OFPErrorMsg received: type=0x%02x code=0x%02x '
      'message=%s',
       msg.type, msg.code, utils.hex_array(msg.data)
     )

  @set_ev_cls(ofp_event.EventOFPEchoRequest, [HANDSHAKE_DISPATCHER, CONFIG_DISPATCHER, MAIN_DISPATCHER])
  def echo_request_handler(self, ev):
    self.last_heartbeat = time.time()

  @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
  def port_status_handler(self, ev):
    msg = ev.msg

    # Currently only the l2 switch is interested in these events.
    self.l2_learning_switch_handler.port_status(msg)
Ejemplo n.º 4
0
class CoscinSwitchApp(frenetic.App):
  client_id = "coscin_switch"
  frenetic_http_host = "localhost"
  frenetic_http_port = "9000"

  # The switch can be in one of three states
  STATE_INITIAL_CONFIG = 0
  STATE_ROUTER_LEARNING = 1
  STATE_NORMAL_OPERATION = 2
  state = STATE_INITIAL_CONFIG

  # Used as destination Mac for ARP replies that we ignore
  BOGUS_MAC = "00:de:ad:00:be:ef"

  def __init__(self, config_file='laptop_demo_network.json'):
    frenetic.App.__init__(self) 
    nib = NetworkInformationBase()
    self.nib = nib
    nib.load_config(config_file)

    self.arp_handler = ArpHandler(self, nib)
    self.intranet_handler = IntranetHandler(self, nib)
    self.cross_campus_handler = CrossCampusHandler(self, nib)
    #self.broadcast_handler = BroadcastHandler(self, nib)

  ##################################
  # Common operations

  # Send payload to all ports  
  def flood(self, switch, except_port, payload):
    flood_to_ports = [ p for p in self.nib.switches[switch] if p != except_port ]
    output_actions = [ Output(Physical(p)) for p in flood_to_ports ]
    # Only bother to send the packet out if there are ports to send it out on.
    if output_actions:
      self.pkt_out(self.nib.switch_to_dpid(switch), payload, output_actions)

  def arp_payload(self, e, pkt):
    p = packet.Packet()
    p.add_protocol(e)
    p.add_protocol(pkt)
    p.serialize()
    return NotBuffered(binascii.a2b_base64(binascii.b2a_base64(p.data)))

  def send_arp_request(self, switch, src_ip, target_ip):
    # It's unclear what the source should be, since the switch has no mac or IP address.
    # It just hears all replies and picks out the interesting stuff.
    src_mac = self.BOGUS_MAC
    dst_mac = "ff:ff:ff:ff:ff:ff"
    e = ethernet.ethernet(dst=dst_mac, src=src_mac, ethertype=ether.ETH_TYPE_ARP)
    pkt = arp.arp_ip(arp.ARP_REQUEST, src_mac, src_ip, "00:00:00:00:00:00", target_ip)
    payload = self.arp_payload(e, pkt)
    logging.info("Sending Arp Request to "+target_ip)
    # None means the request will go out every available port
    self.flood(switch, None, payload)

  def send_arp_request_router_interface(self, switch, target_ip_net):
    # Note this may not or may not be a real host, but the reply will always come back to the switch anyway.
    # TODO: host 250 is appropriate for /24 subnets, but not anything smaller
    src_ip = NetUtils.ip_for_network(target_ip_net, 250)
    dst_ip = NetUtils.ip_for_network(target_ip_net, 1)  # The IP of the router interface will always be a .1
    self.send_arp_request(switch, src_ip, dst_ip)

  def update_and_clear_dirty(self):
    self.update(self.normal_operation_policy())
    self.nib.clear_dirty()

  ########################
  # Frenetic Dispatchers

  def connected(self):
    def handle_current_switches(switches):
      self.nib.save_switches_and_ports(switches)

      logging.info("Connected to Frenetic - Switches: "+self.nib.switch_description())
      logging.info("Installing router learning rules")
      self.update(self.router_learning_policy())

      logging.info("Pausing 2 seconds to allow rules to be installed")
      IOLoop.instance().add_timeout(datetime.timedelta(seconds=2), self.learn_routers)

    # If Frenetic went down, it'll call connected() when it comes back up.  In that case, just 
    # resend the current policies
    if self.state == self.STATE_NORMAL_OPERATION:
      self.update_and_clear_dirty()
    else:
      # Turn on remove_tail_drops to get around issue 463
      #self.config( CompilerOptions("empty", "IP4Dst < EthType < Location < IP4Src < Switch", True, False, True) )
      self.current_switches(callback=handle_current_switches) 

  def packet_in(self, dpid, port, payload):
    if self.state == self.STATE_INITIAL_CONFIG:
      logging.info("Packets received before initialization, dropping" )
    elif self.state == self.STATE_ROUTER_LEARNING:
      self.packet_in_router_learning(dpid, port, payload)
    else:
      self.packet_in_normal_operation(dpid, port, payload)

  # TODO: Bizarrely, port_up events from an HP switch are reported as port_down and vice-versa.
  # I don't know if the problem is in the switch, Frenetic, or the language bindings.  
  def port_down(self, dpid, port):
    switch = self.nib.dpid_to_switch(dpid)
    logging.info("Port up: "+switch+"/"+str(port))
    # If port comes up, remove any learned macs on it (probably won't be any).  This is necessary
    # in case port_down was not fired.   
    if self.nib.unlearn_mac_on_port(switch, port):
      self.update_and_clear_dirty()

  def port_up(self, dpid, port):
    switch = self.nib.dpid_to_switch(dpid)
    logging.info("Port down: "+switch+"/"+str(port))
    # If port goes down, remove any learned macs on it
    if self.nib.unlearn_mac_on_port(switch, port):
      self.update_and_clear_dirty()

  def switch_up(self, dpid, ports):
    switch = self.nib.dpid_to_switch(dpid)
    # If we've seen this switch before, just return.  Otherwise add the ports to unlearned. 
    if self.nib.contains_switch(switch):
      return
    self.nib.add_switches_and_ports(switch, ports)
    self.update_and_clear_dirty()
    logging.info("Updated Switches: "+self.nib.switch_description())

  # Don't remove switch info when it supposedly goes down.  That way if it comes back up 
  # without properly signalling switch_up, we're not over a barrel.  
  def switch_down(self, dpid):
    switch = self.nib.dpid_to_switch(dpid)
    logging.info("Switch down: "+switch)

  ########################
  # Router Learning Mode
  # We assume all router interfaces are up for all alternate paths and that they're fixed for the duration
  # of the controller.  In this mode, we send ARP requests to all paths so we can learn (1) their Mac address 
  # (2) their switch port.  

  # RouterLearningHandler.packet_in
  def packet_in_router_learning(self, dpid, port, payload):
    p = NetUtils.packet(payload, 'arp')
    p_eth = NetUtils.packet(payload, 'ethernet')
    switch = self.nib.dpid_to_switch(dpid)
    logging.info("Received ("+str(p_eth.ethertype)+"): "+p_eth.src+"/"+p.src_ip+" -> ("+switch+", "+str(port)+") -> "+p.dst_ip)

    # Supposedly, the src mac in the ARP reply and the ethernet frame itself should be the
    # the same, but that's not always the case.  The Ethernet frame is definitive. 
    self.nib.learn(switch, self.nib.ROUTER_PORT, port, p_eth.src, p.src_ip)
    self.waiting_for_router_arps -= 1
    if self.waiting_for_router_arps == 0:
      self.normal_mode()

  # RouterLearningHandler.policy
  def router_learning_policy(self):
    # In the intial config, grab all ARP replies for ourselves
    return Filter( Policies.is_arp() ) >> Policies.send_to_controller()

  # RouterLearningHandler.start
  def learn_routers(self):
    logging.info("Switching to Router Learning mode")
    self.state = self.STATE_ROUTER_LEARNING
    self.waiting_for_router_arps = 0
    for side in [ "ithaca", "nyc" ]:
      if self.nib.switch_present(side):
        self.send_arp_request_router_interface(side, self.nib.actual_net_for(side))
        self.waiting_for_router_arps += 1

  ########################
  # Normal Mode
  # This basically handles all traffic afterwards.  It learns host ports coming up.  

  def normal_mode(self):
    logging.info("Switching to Normal mode.  Host ports to learn: "+self.nib.unlearned_ports_description())
    self.update(self.normal_operation_policy())
    self.state = self.STATE_NORMAL_OPERATION

  def normal_operation_policy(self):
    return Union([
      self.arp_handler.policy(),
      #self.broadcast_handler.policy(),
      self.intranet_handler.policy(),
      self.cross_campus_handler.policy()
    ])

  def packet_in_normal_operation(self, dpid, port, payload):
    switch = self.nib.dpid_to_switch(dpid)
    # TODO: deal with non-IP packets, although that's fairly unlikely
    p_eth = NetUtils.packet(payload, 'ethernet')
    if p_eth.ethertype == 0x0806:
      p_arp = NetUtils.packet(payload, 'arp')
      src_ip = p_arp.src_ip
      dst_ip = p_arp.dst_ip
    elif p_eth.ethertype == 0x0800:
      p_ip = NetUtils.packet(payload, 'ipv4')
      src_ip = p_ip.src
      dst_ip = p_ip.dst
    else:
      src_ip = '0.0.0.0'
      dst_ip = '0.0.0.0'
      logging.info("Received packet of type "+str(p_eth.ethertype))

    # TODO: Handle DHCP requests someday, ... maybe
    if src_ip == '0.0.0.0':
      return

    if self.nib.learn(switch, self.nib.ENDHOST_PORT, port, p_eth.src, src_ip):
      self.nib.set_dirty()

    logging.info("Received ("+str(p_eth.ethertype)+"): "+p_eth.src+"/"+src_ip+" -> ("+switch+", "+str(port)+") -> "+dst_ip)

    self.arp_handler.packet_in(dpid, port, payload)
    #self.broadcast_handler.packet_in(dpid, port, payload)
    self.intranet_handler.packet_in(dpid, port, payload)
    self.cross_campus_handler.packet_in(dpid, port, payload)

    if self.nib.is_dirty():
      logging.info("Installing new policy")
      # This doesn't actually wait two seconds, but it seems to serialize the updates so they occur in the right
      # order, as opposed to just calling update_and_clear_dirty on its own.
      IOLoop.instance().add_timeout(datetime.timedelta(seconds=2), self.update_and_clear_dirty)

  def set_preferred_path(self, new_preferred_path):
    # Send pings
    preferred_path = self.nib.get_preferred_path() 
    logging.info("Adjusting preferred path from "+str(preferred_path)+" to "+str(new_preferred_path))
    # Setting a preferred path is pretty hard on the switch - it sends a whole new flow table
    # so don't do it unless absolutely necessary
    if preferred_path == new_preferred_path:
      logging.info("No change.")
    else:
      self.nib.set_preferred_path(new_preferred_path)
      self.update(self.normal_operation_policy())