def fp_affected_volume(G, fp_rejected_policy, srcs, flows): """ """ src_trie = PyTricia() volume = 0 for src in srcs: node_prefixes = G.node[src]['ip-prefixes'] for prefix in node_prefixes: src_trie[prefix] = 1 for flow in flows: if src_trie.get(flow['src_ip'], 0) and \ fp_rejected_policy.get(flow['dst_ip'], {}).get(flow['dst_port'], 0): volume += flow['volume'] return volume
class Topology(NullTopology): def __init__(self, graph, nodes, links, traffic_modulators): self.logger = get_logger('fslib.config') self.__graph = graph self.nodes = nodes self.links = links self.traffic_modulators = traffic_modulators self.routing = {} self.ipdestlpm = None self.owdhash = {} self.__configure_routing() for a,b,d in self.graph.edges(data=True): if 'reliability' in d: self.__configure_edge_reliability(a,b,d['reliability'],d) @property def graph(self): return self.__graph def remove_node(self, name): self.__graph.remove_node(name) for n in self.graph: self.routing[n] = single_source_dijkstra_path(self.graph, n) def __configure_edge_reliability(self, a, b, relistr, edict): relidict = fsutil.mkdict(relistr) ttf = ttr = None for k,v in relidict.iteritems(): if k == 'failureafter': ttf = eval(v) if isinstance(ttf, (int, float)): ttf = modulation_generator([ttf]) elif k == 'downfor': ttr = eval(v) if isinstance(ttr, (int, float)): ttr = modulation_generator([ttr]) elif k == 'mttf': ttf = eval(v) elif k == 'mttr': ttr = eval(v) if ttf or ttr: assert(ttf and ttr) xttf = next(ttf) fscore().after(xttf, 'link-failure-'+a+'-'+b, self.__linkdown, a, b, edict, ttf, ttr) def __configure_routing(self): for n in self.graph: self.routing[n] = single_source_dijkstra_path(self.graph, n) self.ipdestlpm = PyTricia() for n,d in self.graph.nodes_iter(data=True): dlist = d.get('ipdests','').split() for destipstr in dlist: ipnet = ipaddr.IPNetwork(destipstr) xnode = {} self.ipdestlpm[str(ipnet)] = xnode if 'dests' in xnode: xnode['dests'].append(n) else: xnode['net'] = ipnet xnode['dests'] = [ n ] # install static forwarding table entries to each node # FIXME: there's a problematic bit of code here that triggers # pytricia-related (iterator) core dump for nodename,nodeobj in self.nodes.iteritems(): if isinstance(nodeobj, Router): for prefix in self.ipdestlpm.keys(): lpmnode = self.ipdestlpm.get(prefix) if nodename not in lpmnode['dests']: routes = self.routing[nodename] for d in lpmnode['dests']: try: path = routes[d] except KeyError: self.logger.warn("No route from {} to {}".format(nodename, d)) continue nexthop = path[1] nodeobj.addForwardingEntry(prefix, nexthop) self.owdhash = {} for a in self.graph: for b in self.graph: key = a + ':' + b rlist = [ a ] while rlist[-1] != b: nh = self.nexthop(rlist[-1], b) if not nh: self.logger.debug('No route from %s to %s (in owd; ignoring)' % (a,b)) return None rlist.append(nh) owd = 0.0 for i in xrange(len(rlist)-1): owd += self.delay(rlist[i],rlist[i+1]) self.owdhash[key] = owd def node(self, nname): '''get the node object corresponding to a name ''' return self.nodes[nname] def start(self): for tm in self.traffic_modulators: tm.start() for nname,n in self.nodes.iteritems(): n.start() def stop(self): for nname,n in self.nodes.iteritems(): n.stop() def __linkdown(self, a, b, edict, ttf, ttr): '''kill a link & recompute routing ''' self.logger.info('Link failed %s - %s' % (a,b)) self.graph.remove_edge(a,b) self.__configure_routing() uptime = None try: uptime = next(ttr) except: self.logger.info('Link %s-%s permanently taken down (no recovery time remains in generator)' % (a, b)) return else: fscore().after(uptime, 'link-recovery-'+a+'-'+b, self.__linkup, a, b, edict, ttf, ttr) def __linkup(self, a, b, edict, ttf, ttr): '''revive a link & recompute routing ''' self.logger.info('Link recovered %s - %s' % (a,b)) self.graph.add_edge(a,b,weight=edict.get('weight',1),delay=edict.get('delay',0),capacity=edict.get('capacity',1000000)) self.__configure_routing() downtime = None try: downtime = next(ttf) except: self.logger.info('Link %s-%s permanently going into service (no failure time remains in generator)' % (a, b)) return else: fscore().after(downtime, 'link-failure-'+a+'-'+b, self.__linkdown, a, b, edict, ttf, ttr) def owd(self, a, b): '''get the raw one-way delay between a and b ''' key = a + ':' + b rv = None if key in self.owdhash: rv = self.owdhash[key] return rv def delay(self, a, b): '''get the link delay between a and b ''' d = self.graph.edge[a][b] if d is not None: return d.values()[0]['delay'] return None def capacity(self, a, b): '''get the bandwidth between a and b ''' d = self.graph.edge[a][b] if d is not None: return d.values()[0]['capacity'] return None def nexthop(self, node, dest): ''' return the next hop node for a given destination. node: current node dest: dest node name returns: next hop node name ''' try: nlist = self.routing[node][dest] except: return None if len(nlist) == 1: return nlist[0] return nlist[1] def destnode(self, node, dest): ''' return the destination node corresponding to a dest ip. node: current node dest: ipdest returns: destination node name ''' # radix trie lpm lookup for destination IP prefix xnode = self.ipdestlpm.get(dest, None) if xnode: dlist = xnode['dests'] best = None if len(dlist) > 1: # in the case that there are multiple egress nodes # for the same IP destination, choose the closest egress best = None bestw = 10e6 for d in dlist: w = single_source_dijkstra_path_length(self.graph, node, d) if w < bestw: bestw = w best = d else: best = dlist[0] return best else: raise InvalidRoutingConfiguration('No route for ' + dest)
class RouteTable: """ The route table, also known as routing information base (RIB). """ def __init__(self, address_family, fib, log, log_id): assert fib.address_family == address_family self.address_family = address_family self.destinations = PyTricia() self.fib = fib self._log = log self._log_id = log_id def debug(self, msg, *args): if self._log: self._log.debug("[%s] %s" % (self._log_id, msg), *args) def get_route(self, prefix, owner): assert_prefix_address_family(prefix, self.address_family) prefix = ip_prefix_str(prefix) if self.destinations.has_key(prefix): return self.destinations.get(prefix).get_route(owner) return None def put_route(self, rib_route): assert_prefix_address_family(rib_route.prefix, self.address_family) rib_route.stale = False self.debug("Put %s", rib_route) prefix = rib_route.prefix prefix_str = ip_prefix_str(prefix) if not self.destinations.has_key(prefix_str): destination = Destination(self, prefix) self.destinations.insert(prefix_str, destination) else: destination = self.destinations.get(prefix_str) best_changed = destination.put_route(rib_route) if best_changed: child_prefix_strs = self.destinations.children(prefix_str) self.update_fib(prefix, child_prefix_strs) def del_route(self, prefix, owner): assert_prefix_address_family(prefix, self.address_family) prefix_str = ip_prefix_str(prefix) if self.destinations.has_key(prefix_str): destination = self.destinations.get(prefix_str) child_prefix_strs = self.destinations.children(prefix_str) deleted, best_changed = destination.del_route(owner) # If that was the last route for the destination, delete the destination itself if not destination.rib_routes: del self.destinations[prefix_str] else: deleted = False best_changed = False if deleted: self.debug("Delete %s", prefix) else: self.debug("Attempted delete %s (not present)", prefix) if best_changed: self.update_fib(prefix, child_prefix_strs) return deleted def update_fib(self, prefix, child_prefix_strs): # The child_prefix_strs have to be passed as a parameter, because they need to be collected # before the parent is potentially deleted by the calling function. # Locate the best RIB route for the prefix (None if there is no RIB route for the prefix). prefix_str = ip_prefix_str(prefix) if self.destinations.has_key(prefix_str): destination = self.destinations.get(prefix_str) rib_route = destination.best_route() else: rib_route = None # Determine the next-hops for the corresponding FIB route. If the next-hops is an empty # list [] it means it is a discard route. If the next-hops is None it means there should not # be a FIB route. if rib_route: fib_next_hops = rib_route.compute_fib_next_hops() else: fib_next_hops = None # Update the FIB route accordingly. if fib_next_hops is not None: fib_route = FibRoute(prefix, fib_next_hops) self.fib.put_route(fib_route) else: self.fib.del_route(prefix) # Recursively update the FIB for all child routes: their negative next-hops, if any, may # have to be recomputed if a parent route changed. for child_prefix_str in child_prefix_strs: child_destination = self.destinations[child_prefix_str] child_rib_route = child_destination.best_route() child_prefix = child_rib_route.prefix grand_child_prefix_strs = self.destinations.children( child_prefix_str) self.update_fib(child_prefix, grand_child_prefix_strs) def all_routes(self): for prefix in self.destinations: destination = self.destinations.get(prefix) for rib_route in destination.rib_routes: yield rib_route def all_prefix_routes(self, prefix): assert_prefix_address_family(prefix, self.address_family) prefix_str = ip_prefix_str(prefix) if self.destinations.has_key(prefix_str): destination = self.destinations[prefix_str] for rib_route in destination.rib_routes: yield rib_route def cli_table(self): table = Table() table.add_row(RibRoute.cli_summary_headers()) for rib_route in self.all_routes(): table.add_row(rib_route.cli_summary_attributes()) return table def mark_owner_routes_stale(self, owner): # Mark all routes of a given owner as stale. Returns number of routes marked. count = 0 for rib_route in self.all_routes(): if rib_route.owner == owner: rib_route.stale = True count += 1 return count def del_stale_routes(self): # Delete all routes still marked as stale. Returns number of deleted routes. # Cannot delete routes while iterating over routes, so prepare a delete list routes_to_delete = [] for rib_route in self.all_routes(): if rib_route.stale: routes_to_delete.append((rib_route.prefix, rib_route.owner)) # Now delete the routes in the prepared list count = len(routes_to_delete) if count > 0: self.debug("Delete %d remaining stale routes", count) for (prefix, owner) in routes_to_delete: self.del_route(prefix, owner) return count def nr_destinations(self): return len(self.destinations) def nr_routes(self): count = 0 for prefix in self.destinations: destination = self.destinations.get(prefix) count += len(destination.rib_routes) return count
class Router(Node): __slots__ = ['autoack', 'forwarding_table', 'default_link', 'trafgen_ip'] def __init__(self, name, measurement_config, **kwargs): Node.__init__(self, name, measurement_config, **kwargs) self.autoack=bool(eval(str(kwargs.get('autoack','False')))) self.forwarding_table = PyTricia(32) self.default_link = None from fslib.configurator import FsConfigurator ipa,ipb = [ ip for ip in next(FsConfigurator.link_subnetter).iterhosts() ] self.add_link(NullLink, ipa, ipb, 'remote') self.trafgen_ip = str(ipa) def setDefaultNextHop(self, nexthop): '''Set up a default next hop route. Assumes that we just select the first link to the next hop node if there is more than one.''' self.logger.debug("Default: {}, {}".format(nexthop, str(self.node_to_port_map))) self.default_link = self.portFromNexthopNode(nexthop).link if not self.default_link: raise ForwardingFailure("Error setting default next hop: there's no static ARP entry to get interface") self.logger.debug("Setting default next hop for {} to {}".format(self.name, nexthop)) def addForwardingEntry(self, prefix, nexthop): '''Add new forwarding table entry to Node, given a destination prefix and a nexthop (node name)''' pstr = str(prefix) self.logger.debug("Adding forwarding table entry: {}->{}".format(pstr, nexthop)) xnode = self.forwarding_table.get(pstr, None) if not xnode: xnode = [] self.forwarding_table[pstr] = xnode xnode.append(nexthop) def removeForwardingEntry(self, prefix, nexthop): '''Remove an entry from the Node forwarding table.''' pstr = str(prefix) if not self.forwarding_table.has_key(pstr): return xnode = self.forwarding_table.get(pstr) xnode.remove(nexthop) if not xnode: del self.forwarding_table[pstr] def nextHop(self, destip): '''Return the next hop from the local forwarding table (next node, ipaddr), based on destination IP address (or prefix)''' xlist = self.forwarding_table.get(str(destip), None) if xlist: return xlist[hash(destip) % len(xlist)] raise ForwardingFailure() def flowlet_arrival(self, flowlet, prevnode, destnode, input_ip=None): if input_ip is None: input_ip = self.trafgen_ip input_port = self.ports[input_ip] if isinstance(flowlet, SubtractiveFlowlet): killlist = [] ok = [] self.unmeasure_flow(flowlet, prevnode) if destnode != self.name: self.forward(flowlet, destnode) return # a "normal" Flowlet object self.measure_flow(flowlet, prevnode, str(input_port.localip)) if flowlet.endofflow: self.unmeasure_flow(flowlet, prevnode) if destnode == self.name: if self.__should_make_acknowledgement_flow(flowlet): revflow = Flowlet(flowlet.flowident.mkreverse()) revflow.ackflow = True revflow.flowstart = revflow.flowend = fscore().now if flowlet.tcpflags & 0x04: # RST return if flowlet.tcpflags & 0x02: # SYN revflow.tcpflags = revflow.tcpflags | 0x10 # print 'setting syn/ack flags',revflow.tcpflagsstr if flowlet.tcpflags & 0x01: # FIN revflow.tcpflags = revflow.tcpflags | 0x10 # ack revflow.tcpflags = revflow.tcpflags | 0x01 # fin revflow.pkts = flowlet.pkts / 2 # brain-dead ack-every-other revflow.bytes = revflow.pkts * 40 self.measure_flow(revflow, self.name, input_port) # weird, but if reverse flow is short enough, it might only # stay in the flow cache for a very short period of time if revflow.endofflow: self.unmeasure_flow(revflow, prevnode) destnode = fscore().topology.destnode(self.name, revflow.dstaddr) # guard against case that we can't do the autoack due to # no "real" source (i.e., source was spoofed or source addr # has no route) if destnode and destnode != self.name: self.forward(revflow, destnode) else: self.forward(flowlet, destnode) def __should_make_acknowledgement_flow(self, flowlet): return self.autoack and flowlet.ipproto == IPPROTO_TCP and (not flowlet.ackflow) def forward(self, flowlet, destnode): nextnode = self.nextHop(flowlet.dstaddr) port = self.portFromNexthopNode(nextnode, flowkey=flowlet.key) link = port.link or self.default_link link.flowlet_arrival(flowlet, self.name, destnode)
class OpenflowSwitch(Node): __slots__ = ['dpid', 'pox_switch', 'controller_name', 'controller_links', 'ipdests', 'interface_to_port_map', 'trafgen_ip', 'autoack', 'trafgen_mac', 'dstmac_cache', 'trace','tracePkt'] def __init__(self, name, measurement_config, **kwargs): Node.__init__(self, name, measurement_config, **kwargs) self.dpid = abs(hash(name)) self.dstmac_cache = {} self.pox_switch = PoxBridgeSoftwareSwitch(self.dpid, name=name, ports=0, miss_send_len=2**16, max_buffers=2**8, features=None) self.pox_switch.set_connection(self) self.pox_switch.set_output_packet_callback(self. send_packet) self.controller_name = kwargs.get('controller', 'controller') self.autoack = bool(eval(kwargs.get('autoack', 'False'))) self.controller_links = {} self.interface_to_port_map = {} self.trace = bool(eval(kwargs.get('trace', 'False'))) self.tracePkt = bool(eval(kwargs.get('tracePkt','False'))) self.ipdests = PyTricia() for prefix in kwargs.get('ipdests','').split(): self.ipdests[prefix] = True # explicitly add a localhost link/interface ipa,ipb = [ ip for ip in next(FsConfigurator.link_subnetter).iterhosts() ] remotemac = default_ip_to_macaddr(ipb) self.add_link(NullLink, ipa, ipb, 'remote', remotemac=remotemac) self.trafgen_ip = str(ipa) self.trafgen_mac = remotemac self.dstmac_cache[self.name] = remotemac @property def remote_macaddr(self): return self.trafgen_mac def send_packet(self, packet, port_num): '''Forward a data plane packet out a given port''' flet = packet_to_flowlet(packet) # has packet reached destination? if flet is None or self.ipdests.get(flet.dstaddr, None): return pinfo = self.ports[port_num] # self.logger.debug("Switch sending translated packet {}->{} from {}->{} on port {} to {}".format(packet, flet, flet.srcmac, flet.dstmac, port_num, pinfo.link.egress_name)) pinfo.link.flowlet_arrival(flet, self.name, pinfo.remoteip) def send(self, ofmessage): '''Callback function for POX SoftwareSwitch to send an outgoing OF message to controller.''' if not self.started: # self.logger.debug("OF switch-to-controller deferred message {}".format(ofmessage)) evid = 'deferred switch->controller send' fscore().after(0.0, evid, self.send, ofmessage) else: # self.logger.debug("OF switch-to-controller {} - {}".format(str(self.controller_links[self.controller_name]), ofmessage)) clink = self.controller_links[self.controller_name] self.controller_links[self.controller_name].flowlet_arrival(OpenflowMessage(FlowIdent(), ofmessage), self.name, self.controller_name) def set_message_handler(self, *args): '''Dummy callback function for POX SoftwareSwitchBase''' pass def process_packet(self, poxpkt, inputport): '''Process an incoming POX packet. Mainly want to check whether it's an ARP and update our ARP "table" state''' # self.logger.debug("Switch {} processing packet: {}".format(self.name, str(poxpkt))) if poxpkt.type == poxpkt.ARP_TYPE: if poxpkt.payload.opcode == pktlib.arp.REQUEST: self.logger.debug("Got ARP request: {}".format(str(poxpkt.payload))) arp = poxpkt.payload dstip = str(IPv4Address(arp.protodst)) srcip = str(IPv4Address(arp.protosrc)) if dstip in self.interface_to_port_map: portnum = self.interface_to_port_map[dstip] pinfo = self.ports[portnum] if pinfo.remotemac == "ff:ff:ff:ff:ff:ff": pinfo = PortInfo(pinfo.link, pinfo.localip, pinfo.remoteip, pinfo.localmac, str(arp.hwsrc)) self.ports[portnum] = pinfo self.logger.debug("Learned MAC/IP mapping {}->{}".format(arp.hwsrc,srcip)) # self.logger.debug("Updated {} -> {}".format(portnum, self.ports)) def __traceit(self, flowlet, pkt, input_port): # total demeter violation :-( tableentry = self.pox_switch.table.entry_for_packet(pkt, input_port) if tableentry is None: tableentry = 'No Match' self.logger.info("Flow table match for flowlet {} {} {} (packet {}): {}".format(flowlet.srcmac, flowlet.dstmac, str(flowlet), str(pkt), tableentry)) def packetInDebugger(self, flowlet, prevnode, destnode, input_intf): ''' Packet trace logic to trace at the flowlet level ''' # Needs to be changed if controller messages are to be ignored # Wrote this way because it will be easier to extend this to a replay capability input_port = None table_entry = 'No match' actions = None printString = None if isinstance(flowlet,PoxFlowlet): input_port = self.interface_to_port_map[input_intf] table_entry = self.pox_switch.table.entry_for_packet(flowlet.origpkt, input_port) printString = "'{}' to '{}' via '{}'. Flow table match for flowlet {} {} {} (packet {}): {}".format(prevnode, destnode, self.name, flowlet.srcmac, flowlet.dstmac, str(flowlet), flowlet.origpkt, table_entry) else: printString = "'{}' to '{}' via '{}'.".format(prevnode, destnode, self.name) self.logger.info(printString) # from fsdb import pdb as bp # bp.set_trace() def flowlet_arrival(self, flowlet, prevnode, destnode, input_intf=None): '''Incoming flowlet: determine whether it's a data plane flowlet or whether it's an OF message coming back from the controller''' if input_intf is None: input_intf = self.trafgen_ip if self.tracePkt: self.packetInDebugger(flowlet, prevnode, destnode, input_intf) if isinstance(flowlet, OpenflowMessage): self.logger.debug("Received from controller: {}".format(flowlet.ofmsg)) ofmsg = None if isinstance(flowlet.ofmsg, oflib.ofp_base): ofmsg = flowlet.ofmsg elif isinstance(flowlet.ofmsg, str): ofhdr = oflib.ofp_header() ofhdr.unpack(flowlet.ofmsg) ofmsg = oflib._message_type_to_class[ofhdr.header_type]() ofmsg.unpack(flowlet.ofmsg) else: raise UnhandledPoxPacketFlowletTranslation("Not an openflow message from controller: {}".format(flowlet.ofmsg)) self.pox_switch.rx_message(self, ofmsg) if self.trace: for i,entry in enumerate(self.pox_switch.table.entries): actions = '::'.join([oflib.ofp_action_type_map[a.type] + ' ' + repr(a) for a in entry.actions]) self.logger.info("Flow table entry {}: {} (actions: {})".format(i+1, str(entry), actions)) elif isinstance(flowlet, PoxFlowlet): self.logger.debug("Received PoxFlowlet: {}".format(str(flowlet.origpkt))) input_port = self.interface_to_port_map[input_intf] self.process_packet(flowlet.origpkt, input_port) if self.trace: self.__traceit(flowlet, flowlet.origpkt, input_port) self.pox_switch.rx_packet(flowlet.origpkt, input_port) elif isinstance(flowlet, Flowlet): input_port = self.interface_to_port_map[input_intf] portinfo = self.ports[input_port] # self.logger.info("Received flowlet in {} intf{} dstmac{} plocal{} -- {}".format(self.name, input_intf, flowlet.dstmac, portinfo.localmac, type(flowlet))) if portinfo.link is NullLink: flowlet.srcmac = portinfo.remotemac dstmac = self.dstmac_cache.get(destnode, None) if dstmac is None: self.dstmac_cache[destnode] = dstmac = fscore().topology.node(destnode).remote_macaddr flowlet.dstmac = dstmac # self.logger.debug("Local flowlet: setting MAC addrs as {}->{}".format(flowlet.srcmac, flowlet.dstmac)) #else: # # self.logger.debug("Non-local flowlet: MAC addrs {}->{}".format(flowlet.srcmac, flowlet.dstmac)) self.measure_flow(flowlet, prevnode, input_intf) # assume this is an incoming flowlet on the dataplane. # reformat it and inject it into the POX switch # self.logger.debug("Flowlet arrival in OF switch {} {} {} {} {}".format(self.name, flowlet.dstaddr, prevnode, destnode, input_intf)) pkt = flowlet_to_packet(flowlet) pkt.flowlet = None if self.trace: self.__traceit(flowlet, pkt, input_port) self.pox_switch.rx_packet(pkt, input_port) if self.ipdests.get(flowlet.dstaddr, None): self.logger.debug("Flowlet arrived at destination {}".format(self.name)) if self.autoack and not flowlet.ackflow: self.send_ack_flow(flowlet, input_intf) else: raise UnhandledPoxPacketFlowletTranslation("Unexpected message in OF switch: {}".format(type(flowlet))) def send_ack_flow(self, flowlet, input_intf): # print "constructing ack flow:", self.name, flowlet, prevnode, destnode, input_intf revident = flowlet.ident.mkreverse() revflet = Flowlet(revident) revflet.srcmac,revflet.dstmac = flowlet.dstmac,flowlet.srcmac revflet.ackflow = True revflet.pkts = flowlet.pkts/2 revflet.bytes = revflet.pkts * 40 revflet.iptos = flowlet.iptos revflet.tcpflags = flowlet.tcpflags revflet.ingress_intf = input_intf revflet.flowstart = fscore().now revflet.flowend = revflet.flowstart destnode = fscore().topology.destnode(self.name, revflet.dstaddr) # self.logger.debug("Injecting reverse flow: {}->{}".format(revflet.srcmac, revflet.dstmac)) self.flowlet_arrival(revflet, self.name, destnode) def add_link(self, link, localip, remoteip, next_node, remotemac='ff:ff:ff:ff:ff:ff'): localip = str(localip) remoteip = str(remoteip) if next_node == self.controller_name: self.logger.debug("Adding link to {}: {}".format(self.name, link)) self.controller_links[self.controller_name] = link else: portnum = len(self.ports)+1 self.pox_switch.add_port(portnum) ofport = self.pox_switch.ports[portnum] # let pox create local hw_addr and just use it localmac = str(ofport.hw_addr) pi = PortInfo(link, localip, remoteip, localmac, remotemac) self.ports[portnum] = pi self.node_to_port_map[next_node].append(portnum) self.interface_to_port_map[localip] = portnum self.logger.debug("New port in OF switch {}: {}".format(portnum, pi)) def send_gratuitous_arps(self): '''Send ARPs for our own interfaces to each connected node''' for pnum,pinfo in self.ports.iteritems(): # construct an ARP request for one of our known interfaces. # controller isn't included in any of these ports, so these # are only ports connected to other switches arp = pktlib.arp() arp.opcode = pktlib.arp.REQUEST arp.hwsrc = EthAddr(pinfo.localmac) arp.protosrc = int(IPv4Address(pinfo.localip)) arp.protodst = int(IPv4Address(pinfo.remoteip)) ethernet = pktlib.ethernet() ethernet.dst = pktlib.ETHER_BROADCAST ethernet.src = EthAddr(pinfo.localmac) ethernet.payload = arp ethernet.type = ethernet.ARP_TYPE flet = packet_to_flowlet(ethernet) pinfo.link.flowlet_arrival(flet, self.name, pinfo.link.egress_node_name) def start(self): Node.start(self) fscore().after(0.010, "arp {}".format(self.name), self.send_gratuitous_arps) self.logger.debug("OF Switch Startup: {}".format(dpid_to_str(self.pox_switch.dpid))) for p in self.ports: self.logger.debug("\tSwitch port {}: {}, {}".format(p, self.ports[p], self.pox_switch.ports[p].show()))
class Router(Node): __slots__ = ['autoack', 'forwarding_table', 'default_link', 'trafgen_ip'] def __init__(self, name, measurement_config, **kwargs): Node.__init__(self, name, measurement_config, **kwargs) self.autoack=bool(eval(str(kwargs.get('autoack','False')))) self.forwarding_table = PyTricia(32) self.default_link = None from fslib.configurator import FsConfigurator ipa,ipb = [ ip for ip in next(FsConfigurator.link_subnetter).iterhosts() ] self.add_link(NullLink, ipa, ipb, 'remote') self.trafgen_ip = str(ipa) def setDefaultNextHop(self, nexthop): '''Set up a default next hop route. Assumes that we just select the first link to the next hop node if there is more than one.''' self.logger.debug("Default: {}, {}".format(nexthop, str(self.node_to_port_map))) self.default_link = self.portFromNexthopNode(nexthop).link if not self.default_link: raise ForwardingFailure("Error setting default next hop: there's no static ARP entry to get interface") self.logger.debug("Setting default next hop for {} to {}".format(self.name, nexthop)) def addForwardingEntry(self, prefix, nexthop): '''Add new forwarding table entry to Node, given a destination prefix and a nexthop (node name)''' pstr = str(prefix) self.logger.debug("Adding forwarding table entry: {}->{}".format(pstr, nexthop)) xnode = self.forwarding_table.get(pstr, None) if not xnode: xnode = [] self.forwarding_table[pstr] = xnode xnode.append(nexthop) def removeForwardingEntry(self, prefix, nexthop): '''Remove an entry from the Node forwarding table.''' pstr = str(prefix) if not self.forwarding_table.has_key(pstr): return xnode = self.forwarding_table.get(pstr) xnode.remove(nexthop) if not xnode: del self.forwarding_table[pstr] def nextHop(self, flowlet, destnode): '''Return the next hop from the local forwarding table (next node, ipaddr), based on destination IP address (or prefix)''' #if flowlet.fl flowID=str(flowlet.srcaddr)+' '+str(flowlet.dstaddr)+' '+str(flowlet.ipproto)+' '+str(flowlet.srcport)+' ' +str(flowlet.dstport) flowhash=hash(flowID) try: if flowlet.iselephant==False: nexthopNum=networkControlLogic2module.vectorSizeCalc(networkControlLogic2object.RoutingTable[int(self._Node__name.strip('H'))-1][int(destnode.strip('H'))-1]) nexthopIndex=flowhash%nexthopNum nexthop='H'+str(networkControlLogic2object.RoutingTable[int(self._Node__name.strip('H'))-1][int(destnode.strip('H'))-1][nexthopIndex].first+1) else: if networkControlLogic2object.get_FlowCount(int(self._Node__name.strip('H'))-1, flowID)==0: print "NO FLOW ID exists \n" print "flowID ", flowID print " Current Hop ", int(self._Node__name.strip('H'))-1 nexthopNum=networkControlLogic2module.vectorSizeCalc(networkControlLogic2object.RoutingTable[int(self._Node__name.strip('H'))-1][int(destnode.strip('H'))-1]) nexthopIndex=flowhash%nexthopNum nexthop='H'+str(networkControlLogic2object.RoutingTable[int(self._Node__name.strip('H'))-1][int(destnode.strip('H'))-1][nexthopIndex].first+1) else: #nexthop='H'+str(networkControlLogic2object.FlowRoutingTable[int(self._Node__name.strip('H'))-1][flowID].first+1) print "No FlowRouting Tables" except: print "Error finding Nexthop" pass print "Nexthop", nexthop return nexthop #raise ForwardingFailure() def flowlet_arrival(self, flowlet, prevnode, destnode, input_ip=None): if input_ip is None: input_ip = self.trafgen_ip input_port = self.ports[input_ip] if isinstance(flowlet, SubtractiveFlowlet): killlist = [] ok = [] for k,flet in self.flow_table.iteritems(): if next(flowlet.action) and (not flowlet.srcaddr or flet.srcaddr in flowlet.srcaddr) and (not flowlet.dstaddr or flet.dstaddr in flowlet.dstaddr) and (not flowlet.ipproto or flet.ipproto == flowlet.ipproto): killlist.append(k) else: ok.append(k) for kkey in killlist: del self.flow_table[kkey] if destnode != self.name: self.forward(flowlet, destnode) return # a "normal" Flowlet object self.measure_flow(flowlet, prevnode, str(input_port.localip)) if flowlet.endofflow: self.unmeasure_flow(flowlet, prevnode) if destnode==self._Node__name: #print "receivedtrafficvolume ",flowlet.size networkControlLogic2object.trafficVolumeInTransit[int(flowlet.srcaddr.split('.')[3])-1][int(flowlet.dstaddr.split('.')[3])-1]-=flowlet.size print "networkControlLogic2object.trafficVolumeInTransit[",int(flowlet.srcaddr.split('.')[3])-1,"][",int(flowlet.dstaddr.split('.')[3])-1,"]-=",flowlet.size,";" if flowlet.iselephant==True: networkControlLogic2object.elephentsVolumeInTransit[int(flowlet.srcaddr.split('.')[3])-1][int(flowlet.dstaddr.split('.')[3])-1]-=flowlet.size print "networkControlLogic2object.elephentsVolumeInTransit[",int(flowlet.srcaddr.split('.')[3])-1,"][",int(flowlet.dstaddr.split('.')[3])-1,"]-=",flowlet.size,";" if flowlet.endofflow and destnode==self._Node__name and flowlet.iselephant==True: #print "elephantFlowEnd" flowduration=flowlet.flowend-flowlet.flowstart flowID=str(flowlet.key[0])+' '+str(flowlet.key[1])+' '+str(flowlet.key[2])+' '+str(flowlet.key[3])+' '+str(flowlet.key[4]) status=networkControlLogic2object.elephantFlowEnd(flowID,int(flowlet.key[0].split('.')[3])-1,int(flowlet.key[1].split('.')[3])-1,long((fscore().now+flowlet.flowstarttime+flowduration)*(10**12))) print "networkControlLogic2object.elephantFlowEnd('",flowID,"',",int(flowlet.key[0].split('.')[3])-1,",",int(flowlet.key[1].split('.')[3])-1,",",long((fscore().now+flowlet.flowstarttime+flowduration)*(10**12)),");" if status==0: print "printf( \"elephantFlowEnd;Route table Not Changed\");" elif status==1: print "printf( \"elephantFlowEnd;Route table Changed\");" elif status==2: print "printf( \"elephantFlowEnd;Some Error\");" if destnode == self.name: if self.__should_make_acknowledgement_flow(flowlet): revflow = Flowlet(flowlet.flowident.mkreverse()) revflow.ackflow = True revflow.flowstart = revflow.flowend = fscore().now if flowlet.tcpflags & 0x04: # RST return if flowlet.tcpflags & 0x02: # SYN revflow.tcpflags = revflow.tcpflags | 0x10 # print 'setting syn/ack flags',revflow.tcpflagsstr if flowlet.tcpflags & 0x01: # FIN revflow.tcpflags = revflow.tcpflags | 0x10 # ack revflow.tcpflags = revflow.tcpflags | 0x01 # fin revflow.pkts = flowlet.pkts / 2 # brain-dead ack-every-other revflow.bytes = revflow.pkts * 40 self.measure_flow(revflow, self.name, input_port) # weird, but if reverse flow is short enough, it might only # stay in the flow cache for a very short period of time if revflow.endofflow: self.unmeasure_flow(revflow, prevnode) destnode = fscore().topology.destnode(self.name, revflow.dstaddr) # guard against case that we can't do the autoack due to # no "real" source (i.e., source was spoofed or source addr # has no route) if destnode and destnode != self.name: self.forward(revflow, destnode) else: self.forward(flowlet, destnode) def __should_make_acknowledgement_flow(self, flowlet): return self.autoack and flowlet.ipproto == IPPROTO_TCP and (not flowlet.ackflow) def forward(self, flowlet, destnode): nextnode="" try: nextnode = self.nextHop(flowlet,destnode) except: pass # if flowlet.iselephant==True and nextnode=='H'+str(flowlet.dstaddr.split('.')[3]) # status=networkControlLogic2object.elephantFlowEnd(flowID,int(flet.key[0].split('.')[3])-1,int(flet.key[1].split('.')[3])-1,long((fscore().now+flet.flowstarttime+flowduration)*(10**12))) # if status==0: # print "Route table Not Changed" # elif status==1: # print "Route table Changed" # elif status==2: # print "Some Error" port="" if nextnode!="": port = self.portFromNexthopNode(nextnode, flowkey=flowlet.key) if port!="": if port!=None: link = port.link or self.default_link link.flowlet_arrival(flowlet, self.name, destnode)
class Router(Node): __slots__ = ['autoack', 'forwarding_table', 'default_link', 'trafgen_ip'] def __init__(self, name, measurement_config, **kwargs): Node.__init__(self, name, measurement_config, **kwargs) self.autoack=bool(eval(str(kwargs.get('autoack','False')))) self.forwarding_table = PyTricia(32) self.default_link = None from fslib.configurator import FsConfigurator ipa,ipb = [ ip for ip in next(FsConfigurator.link_subnetter).iterhosts() ] self.add_link(NullLink, ipa, ipb, 'remote') self.trafgen_ip = str(ipa) def setDefaultNextHop(self, nexthop): '''Set up a default next hop route. Assumes that we just select the first link to the next hop node if there is more than one.''' self.logger.debug("Default: {}, {}".format(nexthop, str(self.node_to_port_map))) self.default_link = self.portFromNexthopNode(nexthop).link if not self.default_link: raise ForwardingFailure("Error setting default next hop: there's no static ARP entry to get interface") self.logger.debug("Setting default next hop for {} to {}".format(self.name, nexthop)) def addForwardingEntry(self, prefix, nexthop): '''Add new forwarding table entry to Node, given a destination prefix and a nexthop (node name)''' pstr = str(prefix) self.logger.debug("Adding forwarding table entry: {}->{}".format(pstr, nexthop)) xnode = self.forwarding_table.get(pstr, None) if not xnode: xnode = [] self.forwarding_table[pstr] = xnode xnode.append(nexthop) def removeForwardingEntry(self, prefix, nexthop): '''Remove an entry from the Node forwarding table.''' pstr = str(prefix) if not self.forwarding_table.has_key(pstr): return xnode = self.forwarding_table.get(pstr) xnode.remove(nexthop) if not xnode: del self.forwarding_table[pstr] def nextHop(self, destip): '''Return the next hop from the local forwarding table (next node, ipaddr), based on destination IP address (or prefix)''' xlist = self.forwarding_table.get(str(destip), None) if xlist: return xlist[hash(destip) % len(xlist)] raise ForwardingFailure() def flowlet_arrival(self, flowlet, prevnode, destnode, input_ip=None): if input_ip is None: input_ip = self.trafgen_ip input_port = self.ports[input_ip] if isinstance(flowlet, SubtractiveFlowlet): killlist = [] ok = [] for k,flet in self.flow_table.iteritems(): if next(flowlet.action) and (not flowlet.srcaddr or flet.srcaddr in flowlet.srcaddr) and (not flowlet.dstaddr or flet.dstaddr in flowlet.dstaddr) and (not flowlet.ipproto or flet.ipproto == flowlet.ipproto): killlist.append(k) else: ok.append(k) for kkey in killlist: del self.flow_table[kkey] if destnode != self.name: self.forward(flowlet, destnode) return # a "normal" Flowlet object self.measure_flow(flowlet, prevnode, str(input_port.localip)) if flowlet.endofflow: self.unmeasure_flow(flowlet, prevnode) if destnode == self.name: if self.__should_make_acknowledgement_flow(flowlet): revflow = Flowlet(flowlet.flowident.mkreverse()) revflow.ackflow = True revflow.flowstart = revflow.flowend = fscore().now if flowlet.tcpflags & 0x04: # RST return if flowlet.tcpflags & 0x02: # SYN revflow.tcpflags = revflow.tcpflags | 0x10 # print 'setting syn/ack flags',revflow.tcpflagsstr if flowlet.tcpflags & 0x01: # FIN revflow.tcpflags = revflow.tcpflags | 0x10 # ack revflow.tcpflags = revflow.tcpflags | 0x01 # fin revflow.pkts = flowlet.pkts / 2 # brain-dead ack-every-other revflow.bytes = revflow.pkts * 40 self.measure_flow(revflow, self.name, input_port) # weird, but if reverse flow is short enough, it might only # stay in the flow cache for a very short period of time if revflow.endofflow: self.unmeasure_flow(revflow, prevnode) destnode = fscore().topology.destnode(self.name, revflow.dstaddr) # guard against case that we can't do the autoack due to # no "real" source (i.e., source was spoofed or source addr # has no route) if destnode and destnode != self.name: self.forward(revflow, destnode) else: self.forward(flowlet, destnode) def __should_make_acknowledgement_flow(self, flowlet): return self.autoack and flowlet.ipproto == IPPROTO_TCP and (not flowlet.ackflow) def forward(self, flowlet, destnode): nextnode = self.nextHop(flowlet.dstaddr) port = self.portFromNexthopNode(nextnode, flowkey=flowlet.key) link = port.link or self.default_link link.flowlet_arrival(flowlet, self.name, destnode)
class OpenflowSwitch(Node): __slots__ = ['dpid', 'pox_switch', 'controller_name', 'controller_links', 'ipdests', 'interface_to_port_map', 'trafgen_ip', 'autoack', 'trafgen_mac', 'dstmac_cache', 'trace'] def __init__(self, name, measurement_config, **kwargs): Node.__init__(self, name, measurement_config, **kwargs) self.dpid = abs(hash(name)) self.dstmac_cache = {} self.pox_switch = PoxBridgeSoftwareSwitch(self.dpid, name=name, ports=0, miss_send_len=2**16, max_buffers=2**8, features=None) self.pox_switch.set_connection(self) self.pox_switch.set_output_packet_callback(self. send_packet) self.controller_name = kwargs.get('controller', 'controller') self.autoack = bool(eval(kwargs.get('autoack', 'False'))) self.controller_links = {} self.interface_to_port_map = {} self.trace = bool(eval(kwargs.get('trace', 'False'))) self.ipdests = PyTricia() for prefix in kwargs.get('ipdests','').split(): self.ipdests[prefix] = True # explicitly add a localhost link/interface ipa,ipb = [ ip for ip in next(FsConfigurator.link_subnetter).iterhosts() ] remotemac = default_ip_to_macaddr(ipb) self.add_link(NullLink, ipa, ipb, 'remote', remotemac=remotemac) self.trafgen_ip = str(ipa) self.trafgen_mac = remotemac self.dstmac_cache[self.name] = remotemac @property def remote_macaddr(self): return self.trafgen_mac def send_packet(self, packet, port_num): '''Forward a data plane packet out a given port''' flet = packet_to_flowlet(packet) # has packet reached destination? if flet is None or self.ipdests.get(flet.dstaddr, None): return pinfo = self.ports[port_num] # self.logger.debug("Switch sending translated packet {}->{} from {}->{} on port {} to {}".format(packet, flet, flet.srcmac, flet.dstmac, port_num, pinfo.link.egress_name)) pinfo.link.flowlet_arrival(flet, self.name, pinfo.remoteip) def send(self, ofmessage): '''Callback function for POX SoftwareSwitch to send an outgoing OF message to controller.''' if not self.started: # self.logger.debug("OF switch-to-controller deferred message {}".format(ofmessage)) evid = 'deferred switch->controller send' fscore().after(0.0, evid, self.send, ofmessage) else: # self.logger.debug("OF switch-to-controller {} - {}".format(str(self.controller_links[self.controller_name]), ofmessage)) clink = self.controller_links[self.controller_name] self.controller_links[self.controller_name].flowlet_arrival(OpenflowMessage(FlowIdent(), ofmessage), self.name, self.controller_name) def set_message_handler(self, *args): '''Dummy callback function for POX SoftwareSwitchBase''' pass def process_packet(self, poxpkt, inputport): '''Process an incoming POX packet. Mainly want to check whether it's an ARP and update our ARP "table" state''' # self.logger.debug("Switch {} processing packet: {}".format(self.name, str(poxpkt))) if poxpkt.type == poxpkt.ARP_TYPE: if poxpkt.payload.opcode == pktlib.arp.REQUEST: self.logger.debug("Got ARP request: {}".format(str(poxpkt.payload))) arp = poxpkt.payload dstip = str(IPv4Address(arp.protodst)) srcip = str(IPv4Address(arp.protosrc)) if dstip in self.interface_to_port_map: portnum = self.interface_to_port_map[dstip] pinfo = self.ports[portnum] if pinfo.remotemac == "ff:ff:ff:ff:ff:ff": pinfo = PortInfo(pinfo.link, pinfo.localip, pinfo.remoteip, pinfo.localmac, str(arp.hwsrc)) self.ports[portnum] = pinfo self.logger.debug("Learned MAC/IP mapping {}->{}".format(arp.hwsrc,srcip)) # self.logger.debug("Updated {} -> {}".format(portnum, self.ports)) def __traceit(self, flowlet, pkt, input_port): # total demeter violation :-( tableentry = self.pox_switch.table.entry_for_packet(pkt, input_port) if tableentry is None: tableentry = 'No Match' self.logger.info("Flow table match for flowlet {} {} {} (packet {}): {}".format(flowlet.srcmac, flowlet.dstmac, str(flowlet), str(pkt), tableentry)) def flowlet_arrival(self, flowlet, prevnode, destnode, input_intf=None): '''Incoming flowlet: determine whether it's a data plane flowlet or whether it's an OF message coming back from the controller''' if input_intf is None: input_intf = self.trafgen_ip if isinstance(flowlet, OpenflowMessage): self.logger.debug("Received from controller: {}".format(flowlet.ofmsg)) ofmsg = None if isinstance(flowlet.ofmsg, oflib.ofp_base): ofmsg = flowlet.ofmsg elif isinstance(flowlet.ofmsg, str): ofhdr = oflib.ofp_header() ofhdr.unpack(flowlet.ofmsg) ofmsg = oflib._message_type_to_class[ofhdr.header_type]() ofmsg.unpack(flowlet.ofmsg) else: raise UnhandledPoxPacketFlowletTranslation("Not an openflow message from controller: {}".format(flowlet.ofmsg)) self.pox_switch.rx_message(self, ofmsg) if self.trace: for i,entry in enumerate(self.pox_switch.table.entries): actions = '::'.join([oflib.ofp_action_type_map[a.type] + ' ' + repr(a) for a in entry.actions]) self.logger.info("Flow table entry {}: {} (actions: {})".format(i+1, str(entry), actions)) elif isinstance(flowlet, PoxFlowlet): self.logger.debug("Received PoxFlowlet: {}".format(str(flowlet.origpkt))) input_port = self.interface_to_port_map[input_intf] self.process_packet(flowlet.origpkt, input_port) if self.trace: self.__traceit(flowlet, flowlet.origpkt, input_port) self.pox_switch.rx_packet(flowlet.origpkt, input_port) elif isinstance(flowlet, Flowlet): input_port = self.interface_to_port_map[input_intf] portinfo = self.ports[input_port] # self.logger.info("Received flowlet in {} intf{} dstmac{} plocal{} -- {}".format(self.name, input_intf, flowlet.dstmac, portinfo.localmac, type(flowlet))) if portinfo.link is NullLink: flowlet.srcmac = portinfo.remotemac dstmac = self.dstmac_cache.get(destnode, None) if dstmac is None: self.dstmac_cache[destnode] = dstmac = fscore().topology.node(destnode).remote_macaddr flowlet.dstmac = dstmac # self.logger.debug("Local flowlet: setting MAC addrs as {}->{}".format(flowlet.srcmac, flowlet.dstmac)) #else: # # self.logger.debug("Non-local flowlet: MAC addrs {}->{}".format(flowlet.srcmac, flowlet.dstmac)) self.measure_flow(flowlet, prevnode, input_intf) # assume this is an incoming flowlet on the dataplane. # reformat it and inject it into the POX switch # self.logger.debug("Flowlet arrival in OF switch {} {} {} {} {}".format(self.name, flowlet.dstaddr, prevnode, destnode, input_intf)) pkt = flowlet_to_packet(flowlet) pkt.flowlet = None if self.trace: self.__traceit(flowlet, pkt, input_port) self.pox_switch.rx_packet(pkt, input_port) if self.ipdests.get(flowlet.dstaddr, None): self.logger.debug("Flowlet arrived at destination {}".format(self.name)) if self.autoack and not flowlet.ackflow: self.send_ack_flow(flowlet, input_intf) else: raise UnhandledPoxPacketFlowletTranslation("Unexpected message in OF switch: {}".format(type(flowlet))) def send_ack_flow(self, flowlet, input_intf): # print "constructing ack flow:", self.name, flowlet, prevnode, destnode, input_intf revident = flowlet.ident.mkreverse() revflet = Flowlet(revident) revflet.srcmac,revflet.dstmac = flowlet.dstmac,flowlet.srcmac revflet.ackflow = True revflet.pkts = flowlet.pkts/2 revflet.bytes = revflet.pkts * 40 revflet.iptos = flowlet.iptos revflet.tcpflags = flowlet.tcpflags revflet.ingress_intf = input_intf revflet.flowstart = fscore().now revflet.flowend = revflet.flowstart destnode = fscore().topology.destnode(self.name, revflet.dstaddr) # self.logger.debug("Injecting reverse flow: {}->{}".format(revflet.srcmac, revflet.dstmac)) self.flowlet_arrival(revflet, self.name, destnode) def add_link(self, link, localip, remoteip, next_node, remotemac='ff:ff:ff:ff:ff:ff'): localip = str(localip) remoteip = str(remoteip) if next_node == self.controller_name: self.logger.debug("Adding link to {}: {}".format(self.name, link)) self.controller_links[self.controller_name] = link else: portnum = len(self.ports)+1 self.pox_switch.add_port(portnum) ofport = self.pox_switch.ports[portnum] # let pox create local hw_addr and just use it localmac = str(ofport.hw_addr) pi = PortInfo(link, localip, remoteip, localmac, remotemac) self.ports[portnum] = pi self.node_to_port_map[next_node].append(portnum) self.interface_to_port_map[localip] = portnum self.logger.debug("New port in OF switch {}: {}".format(portnum, pi)) def send_gratuitous_arps(self): '''Send ARPs for our own interfaces to each connected node''' for pnum,pinfo in self.ports.iteritems(): # construct an ARP request for one of our known interfaces. # controller isn't included in any of these ports, so these # are only ports connected to other switches arp = pktlib.arp() arp.opcode = pktlib.arp.REQUEST arp.hwsrc = EthAddr(pinfo.localmac) arp.protosrc = int(IPv4Address(pinfo.localip)) arp.protodst = int(IPv4Address(pinfo.remoteip)) ethernet = pktlib.ethernet() ethernet.dst = pktlib.ETHER_BROADCAST ethernet.src = EthAddr(pinfo.localmac) ethernet.payload = arp ethernet.type = ethernet.ARP_TYPE flet = packet_to_flowlet(ethernet) pinfo.link.flowlet_arrival(flet, self.name, pinfo.link.egress_node_name) def start(self): Node.start(self) fscore().after(0.010, "arp {}".format(self.name), self.send_gratuitous_arps) self.logger.debug("OF Switch Startup: {}".format(dpid_to_str(self.pox_switch.dpid))) for p in self.ports: self.logger.debug("\tSwitch port {}: {}, {}".format(p, self.ports[p], self.pox_switch.ports[p].show()))