def reset(self): self.lock.acquire() if USE_LIMITER: self.flow_mod_limiter = Limiter(50) self.dyn_limiter = DynamicLimiter() # OF event stats, in the form of (pkt_count, start_time, end_time). self.pkt_in_stat = (0, None, None) self.flow_mod_stat = (0, None, None) self.pkt_out_stat = (0, None, None) # OF event time queue. Each contains a tuple (time, packet). self.pkt_in_queue = Queue.Queue() self.flow_mod_queue = Queue.Queue() # Maps time at which switch is polled for stats to flow_count. self.flow_stat_interval = 2 # TODO: default 5 self.flow_count_dict = {} # A special packet that "triggers" the special operations. Subsequent # special flow-mod or pkt-out operations will match against this packet. self.trigger_event = None # How long should our garbage pkt-out packets be? self.pkt_out_length = 1500 self.lock.release()
def __init__ (self): self.connection = None self.ports = None self.dpid = None self._listeners = None if RATE_LIMIT: self._dyn_limiter = DynamicLimiter() self._pkt_in_limiter = Limiter(100) self._flow_mod_limiter = Limiter(50)
class Switch (EventMixin): def __init__ (self): self.connection = None self.ports = None self.dpid = None self._listeners = None if RATE_LIMIT: self._dyn_limiter = DynamicLimiter() self._pkt_in_limiter = Limiter(100) self._flow_mod_limiter = Limiter(50) def __repr__ (self): return dpidToStr(self.dpid) def _install (self, switch, port, match, buf = -1): msg = of.ofp_flow_mod() if WILDCARD: _copy_attr(match, msg.match, ['in_port', 'dl_src', 'dl_dst', 'dl_vlan', 'dl_vlan_pcp', 'dl_type', 'nw_tos', 'nw_src', 'nw_dst']) msg.idle_timeout = 0 msg.hard_timeout = 0 else: msg.match = match msg.idle_timeout = 10 msg.hard_timeout = 30 msg.actions.append(of.ofp_action_output(port = port)) print 'Installing flow from', msg.match.dl_src, 'to', msg.match.dl_dst, print 'with output port', port if PKT_OUT: msg.buffer_id = buf if RATE_LIMIT: if self._flow_mod_limiter.to_forward_packet(): switch.connection.send(msg) else: switch.connection.send(msg) def _install_path (self, p, match, buffer_id = -1): for sw,port in p[1:]: self._install(sw, port, match) self._install(p[0][0], p[0][1], match, buffer_id) core.l2_multi.raiseEvent(PathInstalled(p)) def install_path (self, dst_sw, last_port, match, event):#buffer_id, packet): p = _get_path(self, dst_sw, last_port) if p is None: log.warning("Can't get from %s to %s", match.dl_src, match.dl_dst) import pox.lib.packet as pkt if (match.dl_type == pkt.ethernet.IP_TYPE and event.parsed.find('ipv4')): # It's IP -- let's send a destination unreachable log.debug("Dest unreachable (%s -> %s)", match.dl_src, match.dl_dst) from pox.lib.addresses import EthAddr e = pkt.ethernet() e.src = EthAddr(dpidToStr(self.dpid)) #FIXME: Hmm... e.dst = match.dl_src e.type = e.IP_TYPE ipp = pkt.ipv4() ipp.protocol = ipp.ICMP_PROTOCOL ipp.srcip = match.nw_dst #FIXME: Ridiculous ipp.dstip = match.nw_src icmp = pkt.icmp() icmp.type = pkt.ICMP.TYPE_DEST_UNREACH icmp.code = pkt.ICMP.CODE_UNREACH_HOST orig_ip = event.parsed.find('ipv4') d = orig_ip.pack() d = d[:orig_ip.hl * 4 + 8] import struct d = struct.pack("!HH", 0,0) + d #FIXME: MTU icmp.payload = d ipp.payload = icmp e.payload = ipp msg = of.ofp_packet_out() msg.actions.append(of.ofp_action_output(port = event.port)) msg.data = e.pack() self.connection.send(msg) return self._install_path(p, match, event.ofp.buffer_id) log.debug("Installing path for %s -> %s %04x (%i hops)", match.dl_src, match.dl_dst, match.dl_type, len(p)) #log.debug("installing path for %s.%i -> %s.%i" % # (src[0].dpid, src[1], dst[0].dpid, dst[1])) def _handle_PacketIn (self, event): def flood (): """ Floods the packet """ if (not RATE_LIMIT) or \ (RATE_LIMIT and (not SIMPLE_RATE_LIMITER) and self._dyn_limiter.to_forward_packet(DynamicLimiter.PacketType.PktOut)) or \ (RATE_LIMIT and SIMPLE_RATE_LIMITER): msg = of.ofp_packet_out() msg.actions.append(of.ofp_action_output(port = of.OFPP_FLOOD)) msg.buffer_id = event.ofp.buffer_id msg.in_port = event.port self.connection.send(msg) def drop (): # Kill the buffer if event.ofp.buffer_id != -1: msg = of.ofp_packet_out() msg.buffer_id = event.ofp.buffer_id event.ofp.buffer_id = -1 # Mark is dead msg.in_port = event.port self.connection.send(msg) if RATE_LIMIT: if (not SIMPLE_RATE_LIMITER and not self._dyn_limiter.to_forward_packet(DynamicLimiter.PacketType.PktIn)) or \ (SIMPLE_RATE_LIMITER and not self._pkt_in_limiter.to_forward_packet()): return packet = event.parsed loc = (self, event.port) # Place we saw this ethaddr oldloc = mac_map.get(packet.src) # Place we last saw this ethaddr if packet.type == packet.LLDP_TYPE: drop() return if not LEARNING: flood() return #print packet.src,"*",loc,oldloc if oldloc is None: if packet.src.isMulticast() == False: mac_map[packet.src] = loc # Learn position for ethaddr log.debug("Learned %s at %s.%i", packet.src, loc[0], loc[1]) elif oldloc != loc: # ethaddr seen at different place! if loc[1] not in adjacency[loc[0]].values(): # New place is another "plain" port (probably) log.debug("%s moved from %s.%i to %s.%i?", packet.src, dpidToStr(oldloc[0].connection.dpid), oldloc[1], dpidToStr( loc[0].connection.dpid), loc[1]) if packet.src.isMulticast() == False: mac_map[packet.src] = loc # Learn position for ethaddr log.debug("Learned %s at %s.%i", packet.src, loc[0], loc[1]) elif packet.dst.isMulticast() == False: # New place is a switch-to-switch port! #TODO: This should be a flood. It'd be nice if we knew. We could # check if the port is in the spanning tree if it's available. # Or maybe we should flood more carefully? log.warning("Packet from %s arrived at %s.%i without flow",# -- dropping", packet.src, dpidToStr(self.dpid), event.port) #drop() #return if packet.dst.isMulticast(): log.debug("Flood multicast from %s", packet.src) flood() else: if packet.dst not in mac_map: log.debug("%s unknown -- flooding" % (packet.dst,)) flood() else: dest = mac_map[packet.dst] #print packet.dst, "is on", dest match = of.ofp_match.from_packet(packet) self.install_path(dest[0], dest[1], match, event) def disconnect (self): if self.connection is not None: log.debug("Disconnect %s" % (self.connection,)) self.connection.removeListeners(self._listeners) self.connection = None self._listeners = None def connect (self, connection): if self.dpid is None: self.dpid = connection.dpid assert self.dpid == connection.dpid if self.ports is None: self.ports = connection.features.ports self.disconnect() log.debug("Connect %s" % (connection,)) self.connection = connection self._listeners = self.listenTo(connection) def _handle_ConnectionDown (self, event): self.disconnect() pass
class LearningSwitch (EventMixin): def __init__ (self, connection, transparent): self.lock = threading.Lock() # To make sure only one thread is using the socket. self.socket_lock = threading.Lock() # Switch we'll be adding L2 learning switch capabilities to self.connection = connection self.transparent = transparent # We want to hear PacketIn messages, as well as flow-stat messages. self.listenTo(connection) core.openflow.addListenerByName("FlowStatsReceived", self.handle_flow_stats) # Initialize internal state. self.reset() # Constantly checks for table stats. stat_t = threading.Thread(target=self.flow_stat_thread) stat_t.daemon = True stat_t.start() # To allow the experiment's main program to access all the internal # state of this controller. #StateProxyServer(self, self.lock, self.reset).start() TODO: Not used. def reset(self): self.lock.acquire() if USE_LIMITER: self.flow_mod_limiter = Limiter(50) self.dyn_limiter = DynamicLimiter() # OF event stats, in the form of (pkt_count, start_time, end_time). self.pkt_in_stat = (0, None, None) self.flow_mod_stat = (0, None, None) self.pkt_out_stat = (0, None, None) # OF event time queue. Each contains a tuple (time, packet). self.pkt_in_queue = Queue.Queue() self.flow_mod_queue = Queue.Queue() # Maps time at which switch is polled for stats to flow_count. self.flow_stat_interval = 2 # TODO: default 5 self.flow_count_dict = {} # A special packet that "triggers" the special operations. Subsequent # special flow-mod or pkt-out operations will match against this packet. self.trigger_event = None # How long should our garbage pkt-out packets be? self.pkt_out_length = 1500 self.lock.release() def _of_send(self, msg): """ Sends OpenFlow message, thread-safe. """ with self.socket_lock: self.connection.send(msg) def _drop(self, event): if event.ofp.buffer_id != -1: msg = of.ofp_packet_out() msg.buffer_id = event.ofp.buffer_id msg.in_port = event.port self._of_send(msg) def _is_relevant_packet(self, event, packet): """ Sanity check to make sure we deal with experimental traffic only. Otherwise, returns False. """ mylog('zzzz inport =', event.port) if not self.transparent: if packet.type == packet.LLDP_TYPE or packet.dst.isBridgeFiltered(): mylog('pkt_in: Rejected packet LLDP or BridgeFiltered:', packet, repr(packet), dictify(packet)) self._drop(event) return False if event.port not in SWITCH_PORT_LIST: mylog('pkt_in: Rejected packet: invalid port', packet, repr(packet), dictify(packet)) self._drop(event) return False if packet.dst.isMulticast(): self.do_pkt_out(event) return False return True def handle_flow_stats(self, event): """ Each flow in event.stats has the following properties if recursively dictified: {'priority': 32768, 'hard_timeout': 30, 'byte_count': 74, 'length': 96, 'actions': [<pox.openflow.libopenflow_01.ofp_action_output object at 0x7f1420685350>], 'duration_nsec': 14000000, 'packet_count': 1, 'duration_sec': 0, 'table_id': 0, 'match': {'_nw_proto': 6, 'wildcards': 1, '_dl_type': 2048, '_dl_dst': {'_value': '\x00\x19\xb9\xf8\xea\xf8'}, '_nw_src': {'_value': 17449482}, '_tp_dst': 58811, '_dl_vlan_pcp': 0, '_dl_vlan': 65535, '_in_port': 0, '_nw_dst': {'_value': 17318410}, '_dl_src': {'_value': '\x00\x19\xb9\xf9-\xe2'}, '_nw_tos': 0, '_tp_src': 5001}, 'cookie': 0, 'idle_timeout': 10} nw_proto = 17 ==> UDP nw_proto = 6 ==> TCP Adds time->flow_count into the flow_count dictionary. """ flow_count_list = [len([f for f in event.stats if f.table_id == i]) \ for i in range(10)] print 'flow_count_list =', flow_count_list # TODO: xxx mylog('flow_count_list =', flow_count_list) # TODO: xxx for (table_type, table_id) in [('hw', 0), ('sw', 2)]: with open('data/' + table_type + '-table.csv', 'a') as f: print >> f, '%.3f,%d' % (time.time(), flow_count_list[table_id]) with self.lock: self.flow_count_dict[time.time()] = flow_count_list[0] # (flow_mod_count, _, _) = self.flow_mod_stat # # # Find how many rules were promoted. # current_hw_table = set([(f.match._tp_src, f.match._tp_dst) for f in event.stats if f.table_id == 0]) # current_sw_table = set([(f.match._tp_src, f.match._tp_dst) for f in event.stats if f.table_id == 2]) # if not hasattr(self, 'prev_sw_table'): # self.prev_sw_table = set() # promoted_set = self.prev_sw_table & current_hw_table # self.prev_sw_table = current_sw_table # # # Find how many packets sent/received on eth1 # sender_output = run_ssh('ifconfig eth1', hostname='172.22.14.208', stdout=subprocess.PIPE, stderr=subprocess.PIPE, verbose=False).communicate()[0] # sent_KB = int(re.search('TX bytes:(\d+)', sender_output).group(1)) / 1000 # receiver_output = run_ssh('ifconfig eth1', hostname='172.22.14.207', stdout=subprocess.PIPE, stderr=subprocess.PIPE, verbose=False).communicate()[0] # received_KB = int(re.search('RX bytes:(\d+)', receiver_output).group(1)) / 1000 # # # Print space-separated result # print time.time(), len(current_hw_table), len(current_sw_table), len(promoted_set), sent_KB, flow_mod_count, received_KB # # Also write all the flow entries to file. # flow_entries = [(time.time(), f.table_id, f.match._tp_src, f.match._tp_dst, flow_mod_count) for f in event.stats] # with open(FLOW_TABLE_FILE, 'a') as table_f: # print >> table_f, repr(flow_entries) def flow_stat_thread(self): while True: # Send flow stat to switch self._of_send(of.ofp_stats_request(body=of.ofp_flow_stats_request())) # Save timing events to file. with open(TIMING_EVENT_FILE, 'a') as timing_f: for (event_name, queue) in [('pkt_in', self.pkt_in_queue), ('flow_mod', self.flow_mod_queue)]: while not queue.empty(): try: (start_time, packet) = queue.get() except Queue.Empty(): break match = of.ofp_match.from_packet(packet) print >> timing_f, '%.8f,%s,%s,%s' % (start_time, event_name, match.tp_src, match.tp_dst) sleep_time = 0 while True: time.sleep(0.5) sleep_time += 0.5 with self.lock: if sleep_time >= self.flow_stat_interval: break #=============================================================================== # PACKET HANDLERS #=============================================================================== def _handle_PacketIn(self, event): """ Handles packet in messages from the switch to implement above algorithm. """ try: return self._handle_pkt_in_helper(event) except Exception: mylog('*' * 80) mylog('Pkt_in crashed:', traceback.format_exc()) def _handle_pkt_in_helper(self, event): packet = event.parse() current_time = time.time() # There are just packets that we don't care about. if not self._is_relevant_packet(event, packet): return self.pkt_in_queue.put((current_time, packet)) # Count packet-in events only if they're from the pktgen. match = of.ofp_match.from_packet(packet) if (not USE_LIMITER) or (USE_LIMITER and self.dyn_limiter.to_forward_packet(DynamicLimiter.PacketType.PktIn)): if match.tp_src == 10000 and match.tp_dst == 9: with self.lock: (count, start, _) = self.pkt_in_stat if start is None: start = current_time self.pkt_in_stat = (count + 1, start, current_time) # Learn the packet as per normal if there are no trigger events saved. with self.lock: no_trigger_event = self.trigger_event is None if no_trigger_event: self.do_flow_mod(event) def do_flow_mod(self, event=None): """ If the event is not specified, then issues a flow mod with random src and dst ports; all the other fields will match against the trigger event saved earlier. Does not issue pkt_out. Otherwise, does a normal flow_mod. """ msg = of.ofp_flow_mod() # Normal flow-mod if event: msg.match = of.ofp_match.from_packet(event.parse()) msg.actions.append(of.ofp_action_output(port=get_the_other_port(event.port))) msg.buffer_id = event.ofp.buffer_id # Save the trigger event for later matching. if msg.match.tp_dst == TRIGGER_PORT: with self.lock: self.trigger_event = event mylog('Received trigger event. Trigger event.parse() =', pretty_dict(dictify(event.parse()))) mylog('Installed flow:', pretty_dict(dictify(msg.match))) # Special flow-mod that generates random source/dst ports. else: with self.lock: assert self.trigger_event trigger_packet = func_cache(self.trigger_event.parse) msg.match = of.ofp_match.from_packet(trigger_packet) msg.actions.append(of.ofp_action_output(port=get_the_other_port(self.trigger_event.port))) msg.match.tp_dst = random.randint(10, 65000) msg.match.tp_src = random.randint(10, 65000) current_time = time.time() with self.lock: (count, start, _) = self.flow_mod_stat if start is None: start = current_time self.flow_mod_stat = (count + 1, start, current_time) msg.idle_timeout = IDLE_TIMEOUT msg.hard_timeout = HARD_TIMEOUT if (not USE_LIMITER) or (USE_LIMITER and self.flow_mod_limiter.to_forward_packet()): self._of_send(msg) self.flow_mod_queue.put((current_time, event.parse())) def do_pkt_out(self, event=None): msg = of.ofp_packet_out() # Normal pkt-out if event: if event.ofp.buffer_id == -1: mylog("Not flooding unbuffered packet on", dpidToStr(event.dpid)) return msg.actions.append(of.ofp_action_output(port=get_the_other_port(event.port))) msg.buffer_id = event.ofp.buffer_id msg.in_port = event.port mylog('Normal packet-out: ', msg) # Special pkt-out that generates a packet that is exactly the same as # the trigger packet. Unfortunately, only 114 bytes of the original # ingress packet are forwarded into the controller as pkt-in. We need to # make up for the truncated length, if needed. The checksum will be # wrong, but screw that. else: raw_data = func_cache(self.trigger_event.parse).raw if len(raw_data) < self.pkt_out_length: raw_data += 'z' * (self.pkt_out_length - len(raw_data)) msg._data = raw_data msg.buffer_id = -1 with self.lock: assert self.trigger_event msg.actions.append(of.ofp_action_output(port=get_the_other_port(self.trigger_event.port))) msg.in_port = self.trigger_event.port # Stat collection for special pkt-out only. current_time = time.time() with self.lock: (count, start, _) = self.pkt_out_stat if start is None: start = current_time self.pkt_out_stat = (count + 1, start, current_time) if (not USE_LIMITER) or (USE_LIMITER and self.dyn_limiter.to_forward_packet(DynamicLimiter.PacketType.PktOut)): self._of_send(msg) #=============================================================================== # LOOPERS #=============================================================================== def trigger_event_is_ready(self): with self.lock: return self.trigger_event is not None def start_loop_flow_mod(self, interval, max_run_time): self._flow_mod_looper = Looper(self.do_flow_mod, interval, max_run_time) self._flow_mod_looper.start() def stop_loop_flow_mod(self): self._flow_mod_looper.stop() def start_loop_pkt_out(self, interval, max_run_time): self._pkt_out_looper = Looper(self.do_pkt_out, interval, max_run_time) self._pkt_out_looper.start() def stop_loop_pkt_out(self): self._pkt_out_looper.stop()