def arp_for_router(self, dp, switch): # Learning the router port is really important, so we send an ARP packet to poke it into submission # Note that host .2 may not or may not be a real host, but the reply will always come back to the switch anyway. target_ip_net = self.nib.actual_net_for(switch) src_ip = NetUtils.ip_for_network(target_ip_net, 2) dst_ip = NetUtils.ip_for_network(target_ip_net, 1) # The IP of the router interface will always be a .1 OpenflowUtils.send_arp_request(dp, src_ip, dst_ip)
def egress_src_dest_pairs_policy(self): policies = [] for src_dest_pair in self.nib.get_egress_src_dest_pairs(): (src_ip, dst_ip) = src_dest_pair # Convert dst_ip to its real form. First find out what the egress switch actually is: for ap in self.nib.alternate_paths(): if NetUtils.ip_in_network(dst_ip, ap["ithaca"]): switch = "ithaca" imaginary_net = ap["ithaca"] elif NetUtils.ip_in_network(dst_ip, ap["nyc"]): switch = "nyc" imaginary_net = ap["nyc"] real_net = self.nib.actual_net_for(switch) dst_host = NetUtils.host_of_ip(dst_ip, imaginary_net) new_dest_ip = NetUtils.ip_for_network(real_net, dst_host) # If it's not in the ARP cache, it already has an ARP request on the way so ignore it for now. if self.nib.learned_ip(new_dest_ip): direct_net_port = self.nib.port_for_ip(new_dest_ip) new_src_ip = self.nib.translate_alternate_net(src_ip) output_actions = SetIP4Src(new_src_ip) >> SetIP4Dst(new_dest_ip) >> Send(direct_net_port) policies.append( Filter(SwitchEq(self.nib.switch_to_dpid(switch)) & Policies.is_ip_from_to(src_ip, dst_ip)) >> output_actions ) return Union(policies)
def __init__(self, logger, topo_file, routing_table_file): self.logger = logger # Read the Fixed routing table first f = open(routing_table_file, "r") routing_config = json.load(f) f.close() for rt in routing_config: sn = Subnet(rt["subnet"], rt["router_port"], rt["router_mac"], rt["gateway"]) self.subnets.append(sn) # Read the fixed configuration from the topology file topo_agraph = pgv.AGraph(topo_file) switchnames = {} # Read router name and dpid from topo for node in topo_agraph.nodes(): if node.startswith("s"): if node.attr["router"]: router_name = str(node) self.router_dpid = NetUtils.int_from_mac_colon_hex( node.attr["dpid"]) else: switchnames[str(node)] = NetUtils.int_from_mac_colon_hex( node.attr["dpid"]) # Mark all the internal ports in the switches, since these need special attention for link in topo_agraph.edges(): (src_node, dst_node) = link if str(src_node) == router_name: dpid = switchnames[str(dst_node)] if dpid not in self.internal_ports: self.internal_ports[dpid] = [] self.internal_ports[dpid].append(int(link.attr['dport']))
def __init__(self, logger, topo_file, routing_table_file): self.logger = logger # Read the Fixed routing table first f = open(routing_table_file, "r") routing_config = json.load(f) f.close() for rt in routing_config: sn = Subnet(rt["subnet"],rt["router_port"],rt["router_mac"],rt["gateway"]) self.subnets.append(sn) # Read the fixed configuration from the topology file topo_agraph = pgv.AGraph(topo_file) switchnames = {} # Read router name and dpid from topo for node in topo_agraph.nodes(): if node.startswith("s"): if node.attr["router"]: router_name = str(node) self.router_dpid = NetUtils.int_from_mac_colon_hex(node.attr["dpid"]) else: switchnames[str(node)] = NetUtils.int_from_mac_colon_hex(node.attr["dpid"]) # Mark all the internal ports in the switches, since these need special attention for link in topo_agraph.edges(): (src_node, dst_node) = link if str(src_node) == router_name: dpid = switchnames[ str(dst_node) ] if dpid not in self.internal_ports: self.internal_ports[dpid] = [] self.internal_ports[dpid].append(int(link.attr['dport']))
def coscin_net_for(self, src_ip): for side in [ "ithaca", "nyc" ]: if NetUtils.ip_in_network(src_ip, self.actual_net_for(side)): return self.actual_net_for(side) for ap in self.alternate_paths(): if NetUtils.ip_in_network(src_ip, ap[side]): return ap[side] return None
def coscin_net_for(self, src_ip): for side in ["ithaca", "nyc"]: if NetUtils.ip_in_network(src_ip, self.actual_net_for(side)): return self.actual_net_for(side) for ap in self.alternate_paths(): if NetUtils.ip_in_network(src_ip, ap[side]): return ap[side] return None
def ip_in_coscin_network(self, dst_ip): if NetUtils.ip_in_network(dst_ip, self.actual_net_for("ithaca")): return True if NetUtils.ip_in_network(dst_ip, self.actual_net_for("nyc")): return True for ap in self.alternate_paths(): if NetUtils.ip_in_network(dst_ip, ap["ithaca"]) or NetUtils.ip_in_network(dst_ip, ap["nyc"]): return True return False
def packet_in(self, dpid, port, payload): p_eth = NetUtils.packet(payload, 'ethernet') if p_eth.ethertype != 0x0806: return # Handle ARP requests. p_arp = NetUtils.packet(payload, 'arp') src_ip = p_arp.src_ip dst_ip = p_arp.dst_ip switch = self.nib.dpid_to_switch(dpid) if p_arp.opcode == arp.ARP_REQUEST: preferred_path = self.nib.get_preferred_path() # If the request is for a host in the net we're already in, just broadcast it. The host # itself will answer. if NetUtils.ip_in_network(src_ip, self.nib.actual_net_for(switch)) and \ NetUtils.ip_in_network(dst_ip, self.nib.actual_net_for(switch)): real_dest_ip = None else: # It's an imaginary host on one of the alternate paths real_dest_ip = self.nib.translate_alternate_net(dst_ip) if real_dest_ip == None: logging.info("Flooding ARP Request") self.main_app.flood(switch, port, payload) elif self.nib.learned_ip(real_dest_ip): real_dest_mac = self.nib.mac_for_ip(real_dest_ip) self.arp_reply(switch, port, p_eth.src, src_ip, real_dest_mac, dst_ip) else: # Send an ARP request to all ports, then just stay out of the way. If the host is up # on an unlearned port, it'll send a response, and that'll trigger learning. Then # when the NEXT ARP request for this address is received (it'll get retried a bunch of # times in practice), the reply can be generated from the ARP cache. # It doesn't matter so much where the ARP reply goes, because this switch will pick it up. switch_net = self.nib.actual_net_for(switch) # TODO: 250 will work as a host on subnets with a /24, but not any higher. src_ip = NetUtils.ip_for_network(switch_net, 250) self.main_app.send_arp_request(switch, src_ip, real_dest_ip) # We don't do anything special to ARP replies, just forward them onto their destination # unidirectionally # TODO: Can't this be handled by L2Switch, since it's bound for a real Mac? elif p_arp.opcode == arp.ARP_REPLY: # We ignore the text of ARP replies bound for us. We just used them for learning the port. if p_eth.dst == self.main_app.BOGUS_MAC: pass # The destination port was definitely learned because that's where the request originated elif not self.nib.seen_mac(p_eth.dst): logging.error("Ooops! ARP reply bound for a destination we don't know") return elif self.nib.switch_for_mac(p_eth.dst) != switch: logging.error("Ooops! ARP reply is destined for a different network. Can't happen.") return else: direct_net_port = self.nib.port_for_mac(p_eth.dst) output_actions = [Output(Physical(direct_net_port))] self.main_app.pkt_out(dpid, payload, output_actions)
def ip_in_coscin_network(self, dst_ip): if NetUtils.ip_in_network(dst_ip, self.actual_net_for("ithaca")): return True if NetUtils.ip_in_network(dst_ip, self.actual_net_for("nyc")): return True for ap in self.alternate_paths(): if NetUtils.ip_in_network(dst_ip, ap["ithaca"]) or NetUtils.ip_in_network( dst_ip, ap["nyc"]): return True return False
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()
def add_outgoing_dynamic_flow(self, msg): dp = msg.datapath switch = self.nib.switch_for_dp(dp) ofproto = dp.ofproto parser = dp.ofproto_parser in_port = msg.match['in_port'] pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] ip = pkt.get_protocols(ipv4.ipv4)[0] src_ip = ip.src dst_ip = ip.dst actions = [] # We only add IP rewriting for Coscin packets. All non-coscin packets will get a flow rule, but no # IP rewriting. if self.nib.ip_rewriting() and self.nib.ip_in_coscin_network(dst_ip): opposite_switch = self.nib.opposite_switch(switch) # If this is bound for the "virtual" network on the other side, pick the path and rewrite # the destination IP's if NetUtils.ip_in_network( dst_ip, self.nib.actual_net_for(opposite_switch)): new_src_ip = self.nib.translate_ip( src_ip, self.nib.preferred_net(switch)) actions.append(parser.OFPActionSetField(ipv4_src=new_src_ip)) new_dst_ip = self.nib.translate_ip( dst_ip, self.nib.preferred_net(opposite_switch)) actions.append(parser.OFPActionSetField(ipv4_dst=new_dst_ip)) # If it's trying to communicate with the router on the same side (like ping), just let it # through elif NetUtils.ip_in_network(dst_ip, self.nib.actual_net_for(switch)): pass else: # If it's a direct route (e.g 56.100 -> 157.200), we only need to renumber the source. # But we have to select the right imaginary net so the path is "straight" new_src_ip = self.nib.translate_ip( src_ip, self.nib.opposite_net_for(dst_ip)) actions.append(parser.OFPActionSetField(ipv4_src=new_src_ip)) # No matter what, our rule will always send the packet to the router actions.append( parser.OFPActionOutput(self.nib.router_port_for_switch(switch))) # Only TCP and UDP packets are handled by installing rules in custom pipleine hash table. # (But we had to compute the actios regardless because they'll be used in a Packet Out) if ip.proto == in_proto.IPPROTO_TCP or ip.proto == in_proto.IPPROTO_UDP: self.write_table_2_rule(switch, dp, ip, pkt, actions, "outgoing") return actions
def send_along_direct_path(self, switch, src_ip, dst_ip, payload): opposite_switch = self.nib.opposite_switch(switch) for ap in self.nib.alternate_paths(): if NetUtils.ip_in_network(dst_ip, ap[opposite_switch]): src_net = ap[switch] src_host = NetUtils.host_of_ip(src_ip, self.nib.actual_net_for(switch)) # Translate this to the direct path IP new_src = NetUtils.ip_for_network(src_net, src_host) output_actions = [SetIP4Src(new_src), Output(Physical(self.nib.router_port_for_switch(switch)))] dpid = self.nib.switch_to_dpid(switch) self.main_app.pkt_out(dpid, payload, output_actions)
def translate_alternate_net(self, dst_ip): # First find out which side (ithaca or nyc) it's on found_side = None for ap in self.alternate_paths(): for side in ["ithaca", "nyc"]: if NetUtils.ip_in_network(dst_ip, ap[side]): found_side = side imaginary_net = ap[side] if side == None: logging.error("Ooops. Got an ARP request for a net we don't know about. Oh well.") return False else: host = NetUtils.host_of_ip(dst_ip, imaginary_net) return NetUtils.ip_for_network(self.actual_net_for(found_side), host)
def opposite_net_for(self, src_ip): for ap in self.alternate_paths(): for side in [ "ithaca", "nyc" ]: opposite_side = self.opposite_switch(side) if NetUtils.ip_in_network(src_ip, ap[opposite_side]): return ap[side] return None
def opposite_net_for(self, src_ip): for ap in self.alternate_paths(): for side in ["ithaca", "nyc"]: opposite_side = self.opposite_switch(side) if NetUtils.ip_in_network(src_ip, ap[opposite_side]): return ap[side] return None
def add_outgoing_dynamic_flow(self, msg): dp = msg.datapath switch = self.nib.switch_for_dp(dp) ofproto = dp.ofproto parser = dp.ofproto_parser in_port = msg.match['in_port'] pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] ip = pkt.get_protocols(ipv4.ipv4)[0] src_ip = ip.src dst_ip = ip.dst actions = [ ] # We only add IP rewriting for Coscin packets. All non-coscin packets will get a flow rule, but no # IP rewriting. if self.nib.ip_rewriting() and self.nib.ip_in_coscin_network(dst_ip): opposite_switch = self.nib.opposite_switch(switch) # If this is bound for the "virtual" network on the other side, pick the path and rewrite # the destination IP's if NetUtils.ip_in_network(dst_ip, self.nib.actual_net_for(opposite_switch)): new_src_ip = self.nib.translate_ip(src_ip, self.nib.preferred_net(switch)) actions.append( parser.OFPActionSetField( ipv4_src = new_src_ip ) ) new_dst_ip = self.nib.translate_ip(dst_ip, self.nib.preferred_net(opposite_switch)) actions.append( parser.OFPActionSetField( ipv4_dst = new_dst_ip ) ) # If it's trying to communicate with the router on the same side (like ping), just let it # through elif NetUtils.ip_in_network(dst_ip, self.nib.actual_net_for(switch)): pass else: # If it's a direct route (e.g 56.100 -> 157.200), we only need to renumber the source. # But we have to select the right imaginary net so the path is "straight" new_src_ip = self.nib.translate_ip(src_ip, self.nib.opposite_net_for(dst_ip)) actions.append( parser.OFPActionSetField( ipv4_src = new_src_ip ) ) # No matter what, our rule will always send the packet to the router actions.append(parser.OFPActionOutput(self.nib.router_port_for_switch(switch))) # Only TCP and UDP packets are handled by installing rules in custom pipleine hash table. # (But we had to compute the actios regardless because they'll be used in a Packet Out) if ip.proto == in_proto.IPPROTO_TCP or ip.proto == in_proto.IPPROTO_UDP: self.write_table_2_rule(switch, dp, ip, pkt, actions, "outgoing" ) return actions
def capture_new_source_dest_pairs_policy(self): policies = [] for switch in self.nib.switches_present(): dpid = self.nib.switch_to_dpid(switch) for endhost in self.nib.get_endhosts(switch): (host, host_port, host_mac, host_ip) = endhost opposite_switch = self.nib.opposite_switch(switch) dest_cdr = self.nib.actual_net_for(opposite_switch) (dest_net, dest_mask) = NetUtils.net_mask(dest_cdr) # Note we really don't have to test for source IP, but it's extra security policies.append( Filter( Policies.at_switch_port(dpid, host_port) & Policies.is_ip() & IP4SrcEq(host_ip) & IP4DstEq(dest_net, dest_mask) & self.destination_not_known_host_on_net(host_ip, dest_cdr) ) >> Policies.send_to_controller() ) for ap in self.nib.alternate_paths(): dest_cdr = ap[opposite_switch] (dest_net, dest_mask) = NetUtils.net_mask(dest_cdr) policies.append( Filter( Policies.at_switch_port(dpid, host_port) & Policies.is_ip() & IP4SrcEq(host_ip) & IP4DstEq(dest_net, dest_mask) & self.destination_not_known_host_on_net(host_ip, dest_cdr) ) >> Policies.send_to_controller() ) # Now handle incoming packets for all our home networks. We need to learn # those (src, dest) pairs as well for ap in self.nib.alternate_paths(): dest_cdr = ap[switch] (dest_net, dest_mask) = NetUtils.net_mask(dest_cdr) policies.append( Filter(Policies.is_ip() & IP4DstEq(dest_net, dest_mask) & self.src_dest_pair_not_learned(dest_cdr)) >> Policies.send_to_controller() ) return Union(policies)
def send_along_preferred_path(self, switch, src_ip, dst_ip, payload): # Get host from src_ip src_pref_net = self.nib.preferred_net(switch) src_host = NetUtils.host_of_ip(src_ip, self.nib.actual_net_for(switch)) # Translate this to the preferred path IP new_src = NetUtils.ip_for_network(src_pref_net, src_host) # And do the same for the destination opposite_switch = self.nib.opposite_switch(switch) dest_host = NetUtils.host_of_ip(dst_ip, self.nib.actual_net_for(opposite_switch)) new_dest = NetUtils.ip_for_network(self.nib.preferred_net(opposite_switch), dest_host) output_actions = [ SetIP4Src(new_src), SetIP4Dst(new_dest), Output(Physical(self.nib.router_port_for_switch(switch))), ] dpid = self.nib.switch_to_dpid(switch) self.main_app.pkt_out(dpid, payload, output_actions)
def get_net_info(): ''' Get host information on current machine return dictionary of host information ''' import socket results = dict() try: host_infos = socket.gethostbyaddr(socket.gethostname()) results['hostname'] = host_infos[0] results['host_alias'] = ', '.join(host_infos[1]) results['ip_address_alternate'] = ', '.join(host_infos[2]) net_util = NetUtils(10) results['ip_address'] = net_util.get_host_ip() except: print('Failed to get net info') #print traceback.print_exc() return results
def packet_in(self, dpid, port, payload): p_eth = NetUtils.packet(payload, "ethernet") if p_eth.ethertype != 0x0800: return p_ip = NetUtils.packet(payload, "ipv4") src_ip = p_ip.src dst_ip = p_ip.dst switch = self.nib.dpid_to_switch(dpid) # If we haven't seen this source, dest pair yet, add it, and the rule with it. # Which list we put it in depends on whether we're at the ingress or egress switch if self.nib.at_ingress_switch(switch, port): # TODO: If this packet is bound for hosts outside the CoSciN network, in production just forward them, # For now, just drop them. if not self.nib.ip_in_coscin_network(dst_ip): logging.info("Internet-bound packet dropped in this test network") return # It's possible that this is an intra-network packet even though the rule should 've been installed # to handle such packets directly. send_along_direct_path will handle it below. elif NetUtils.ip_in_network(dst_ip, self.nib.actual_net_for(switch)): pass elif not self.nib.seen_src_dest_pair_at_ingress(src_ip, dst_ip): self.nib.add_ingress_src_dest_pair(src_ip, dst_ip) self.nib.set_dirty() elif self.nib.at_egress_switch(switch, port): if not self.nib.seen_src_dest_pair_at_egress(src_ip, dst_ip): self.nib.add_egress_src_dest_pair(src_ip, dst_ip) self.nib.set_dirty() # If we have seen it, the rule should've taken care of the next packets, but it might # not be in effect yet so we handle it manually opposite_switch = self.nib.opposite_switch(switch) if NetUtils.ip_in_network(dst_ip, self.nib.actual_net_for(opposite_switch)): self.send_along_preferred_path(switch, src_ip, dst_ip, payload) elif self.nib.at_ingress_switch(switch, port): if NetUtils.ip_in_network(dst_ip, self.nib.actual_net_for(switch)): self.send_to_host_without_rewrite(switch, src_ip, dst_ip, payload) else: self.send_along_direct_path(switch, src_ip, dst_ip, payload) elif self.nib.at_egress_switch(switch, port): self.send_to_host(switch, src_ip, dst_ip, payload)
def learn(self, switch, port_type, port, mac, src_ip): if self.learned(switch, port): return False logging.info("Learning: "+mac+"/"+src_ip+" attached to ( "+switch+", "+str(port)+" )") self.hosts[mac] = (switch, port) self.arp_cache[src_ip] = (switch, port, mac) self.unlearned_ports[switch].remove(port) if port_type == self.ENDHOST_PORT: host_portion = NetUtils.host_of_ip(src_ip, self.coscin_config[switch]["network"]) self.endhosts[switch].append( (host_portion, port, mac, src_ip) ) # We also add entries for this host on all its imaginary paths for ap in self.coscin_config["alternate_paths"]: virtual_ip = NetUtils.ip_for_network(ap[switch], host_portion) self.arp_cache[virtual_ip] = (switch, port, mac) elif port_type == self.ROUTER_PORT: self.router_port[switch] = port else: logging.error("Unknown port type: "+str(port_type)) return False return True
def destination_not_known_host_on_net(self, host_ip, dest_net): # Given an IP, find all src_dest pairs we've seen for this src, filter the dests down to # those on the dest_net, and return a clause that doesn't match any of them preds = [] for src_dest_pair in self.nib.get_ingress_src_dest_pairs(): (src_ip, dst_ip) = src_dest_pair if src_ip == host_ip and NetUtils.ip_in_network(dst_ip, dest_net): preds.append(IP4DstEq(dst_ip)) if not preds: return true else: return Not(Or(preds))
def src_dest_pair_not_learned(self, dest_net): # Given a destination net, find all learned src, dest pairs that match it and return # a clause that doesn't match any of them. preds = [] for src_dest_pair in self.nib.get_egress_src_dest_pairs(): (src_ip, dst_ip) = src_dest_pair if NetUtils.ip_in_network(dst_ip, dest_net): preds.append(IP4SrcEq(src_ip) & IP4DstEq(dst_ip)) if not preds: return true else: return Not(Or(preds))
def send_to_host(self, switch, src_ip, dst_ip, payload): # Convert dst_ip to its real form. First find out what the egress switch actually is: for ap in self.nib.alternate_paths(): if NetUtils.ip_in_network(dst_ip, ap[switch]): imaginary_net = ap[switch] real_net = self.nib.actual_net_for(switch) dst_host = NetUtils.host_of_ip(dst_ip, imaginary_net) new_dest_ip = NetUtils.ip_for_network(real_net, dst_host) # If we don't know the port for this address (which might happen if the # IP is on this network, but the host isn't up or doesn't exist) there's not # much we can do with this packet. Send an ARP request and hope the # original packet gets retransmitted (which is normally the case) if not self.nib.learned_ip(new_dest_ip): src_ip = NetUtils.ip_for_network(real_net, 250) self.main_app.send_arp_request(switch, src_ip, new_dest_ip) else: direct_net_port = self.nib.port_for_ip(new_dest_ip) # We also need to translate the alternately-numbered net to a real one. Otherwise the # host (which only knows real networks) may not know what to do with it. new_src_ip = self.nib.translate_alternate_net(src_ip) output_actions = [SetIP4Src(new_src_ip), SetIP4Dst(new_dest_ip), Output(Physical(direct_net_port))] dpid = self.nib.switch_to_dpid(switch) self.main_app.pkt_out(dpid, payload, output_actions)
def add_incoming_dynamic_flow(self, msg): dp = msg.datapath switch = self.nib.switch_for_dp(dp) ofproto = dp.ofproto parser = dp.ofproto_parser pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] ip = pkt.get_protocols(ipv4.ipv4)[0] src_ip = ip.src dst_ip = ip.dst # If the destination IP is in the real network (ip_in_network returns true), # then the other side of the network is not properly rewriting the destination. # or the packet is coming from the non-Coscin Internet. Just leave those IP's alone (we assume # there's nothing crazy like the source using a virtual address here.) actions = [] if self.nib.ip_rewriting() and not NetUtils.ip_in_network( dst_ip, self.nib.actual_net_for(switch)): opposite_switch = self.nib.opposite_switch(switch) # Check the source IP. It should be coming from the same path subnets as the detination. If it's not, # it's some arbitrary Internet host trying to contact the imaginary net. Drop the packet like a hot potato. new_src_ip = self.nib.translate_ip( src_ip, self.nib.actual_net_for(opposite_switch)) if new_src_ip == None: return [] actions.append(parser.OFPActionSetField(ipv4_src=new_src_ip)) # The destination IP is always on one of the imaginary nets, or else it wouldn't have been delivered # to this switch at all. new_dst_ip = self.nib.translate_ip(dst_ip, self.nib.actual_net_for(switch)) actions.append(parser.OFPActionSetField(ipv4_dst=new_dst_ip)) # The destination mac in the packet is guaranteed OK because the router placed it there as a result # of its ARP cache. However, that doesn't necessarily mean we have learned that port yet, so act like # an L2 switch. But in that case, don't install the rule because we don't want to just flood the switch # everytime it happens. output_p = self.nib.port_for_mac(eth.dst) if output_p == None: output_p = ofproto.OFPP_FLOOD actions.append(parser.OFPActionOutput(output_p)) if (ip.proto == in_proto.IPPROTO_TCP or ip.proto == in_proto.IPPROTO_UDP) and output_p != ofproto.OFPP_FLOOD: self.write_table_2_rule(switch, dp, ip, pkt, actions, "incoming") return actions
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 send_to_host_without_rewrite(self, switch, src_ip, dst_ip, payload): # Convert dst_ip to its real form. First find out what the egress switch actually is: real_net = self.nib.actual_net_for(switch) # If we don't know the port for this address (which might happen if the # IP is on this network, but the host isn't up or doesn't exist) just send # an ARP request. Alternatively, we could flood it out, but ARP has the advantage # of making the port get learned. if not self.nib.learned_ip(dst_ip): arp_src_ip = NetUtils.ip_for_network(real_net, 250) self.main_app.send_arp_request(switch, arp_src_ip, dst_ip) else: direct_net_port = self.nib.port_for_ip(dst_ip) output_actions = [Output(Physical(direct_net_port))] dpid = self.nib.switch_to_dpid(switch) self.main_app.pkt_out(dpid, payload, output_actions)
def packet_in(self, msg): # If we're not doing any IP rewriting, the ARP requests and replies should have been handled by the # the L2 switch. if not self.nib.ip_rewriting(): return cookie = msg.cookie # Ignore all packets that came here by other rules than the ARP rule if cookie != self.ARP_RULE: return dp = msg.datapath pkt = packet.Packet(msg.data) p_eth = pkt.get_protocols(ethernet.ethernet)[0] p_arp = pkt.get_protocols(arp.arp)[0] src_ip = p_arp.src_ip dst_ip = p_arp.dst_ip switch = self.nib.switch_for_dp(dp) in_port = msg.match['in_port'] # We only handle ARP requests for the virtual net here. All ARP replies and requests were # actually forwarded by l2_switch_handler, but no one will answer those for the virtual net. # We formulate ARP responses for those requests here. if p_arp.opcode == arp.ARP_REQUEST: # Translate virtual address to a real one real_dest_ip = self.nib.translate_ip( dst_ip, self.nib.actual_net_for(switch)) if real_dest_ip == None: pass elif self.nib.learned_ip(real_dest_ip): real_dest_mac = self.nib.mac_for_ip(real_dest_ip) OpenflowUtils.send_arp_reply(dp, in_port, p_eth.src, src_ip, real_dest_mac, dst_ip) else: # Send an ARP request to all ports, then just stay out of the way. If the host is up # on an unlearned port, it'll send a response, and that'll trigger learning. Then # when the NEXT ARP request for this address is received (it'll get retried a bunch of # times in practice), the reply can be generated from the ARP cache. # It doesn't matter so much where the ARP reply goes, because this switch will pick it up. switch_net = self.nib.actual_net_for(switch) src_ip = NetUtils.ip_for_network(switch_net, 2) OpenflowUtils.send_arp_request(dp, src_ip, real_dest_ip)
def add_incoming_dynamic_flow(self, msg): dp = msg.datapath switch = self.nib.switch_for_dp(dp) ofproto = dp.ofproto parser = dp.ofproto_parser pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] ip = pkt.get_protocols(ipv4.ipv4)[0] src_ip = ip.src dst_ip = ip.dst # If the destination IP is in the real network (ip_in_network returns true), # then the other side of the network is not properly rewriting the destination. # or the packet is coming from the non-Coscin Internet. Just leave those IP's alone (we assume # there's nothing crazy like the source using a virtual address here.) actions = [] if self.nib.ip_rewriting() and not NetUtils.ip_in_network(dst_ip, self.nib.actual_net_for(switch)): opposite_switch = self.nib.opposite_switch(switch) # Check the source IP. It should be coming from the same path subnets as the detination. If it's not, # it's some arbitrary Internet host trying to contact the imaginary net. Drop the packet like a hot potato. new_src_ip = self.nib.translate_ip(src_ip, self.nib.actual_net_for(opposite_switch)) if new_src_ip == None: return [] actions.append( parser.OFPActionSetField(ipv4_src=new_src_ip) ) # The destination IP is always on one of the imaginary nets, or else it wouldn't have been delivered # to this switch at all. new_dst_ip = self.nib.translate_ip(dst_ip, self.nib.actual_net_for(switch)) actions.append( parser.OFPActionSetField(ipv4_dst=new_dst_ip) ) # The destination mac in the packet is guaranteed OK because the router placed it there as a result # of its ARP cache. However, that doesn't necessarily mean we have learned that port yet, so act like # an L2 switch. But in that case, don't install the rule because we don't want to just flood the switch # everytime it happens. output_p = self.nib.port_for_mac(eth.dst) if output_p == None: output_p = ofproto.OFPP_FLOOD actions.append(parser.OFPActionOutput(output_p)) if (ip.proto == in_proto.IPPROTO_TCP or ip.proto == in_proto.IPPROTO_UDP) and output_p != ofproto.OFPP_FLOOD: self.write_table_2_rule(switch, dp, ip, pkt, actions, "incoming" ) return actions
def policy(self): policies = [] for switch in self.nib.switches_present(): dpid = self.nib.switch_to_dpid(switch) # In normal mode, we capture ARP requests for IP's that don't really exist. You can # think of them as symbolic links to the real IP. We capture .1 address of # each of the endpoint networks, plus any real hosts on the net # And we capture ARP requests for the alternate paths. These will always be for # hosts that have no real estate on the imaginary link, as in 192.168.156.100 along # the 192.168.156.* imaginary network. This will be translated to the real net 192.168.56.100 # Note: only the routers actually send these requests, not end hosts, who always send them # to a default gateway. for ap in self.nib.alternate_paths(): (net, mask) = NetUtils.net_mask(ap[switch]) policies.append(Filter(Policies.at_switch(dpid) & Policies.is_arp() & IP4DstEq(net,mask)) >> Policies.send_to_controller()) return Union(policies)
def packet_in(self, msg): # If we're not doing any IP rewriting, the ARP requests and replies should have been handled by the # the L2 switch. if not self.nib.ip_rewriting(): return cookie = msg.cookie # Ignore all packets that came here by other rules than the ARP rule if cookie != self.ARP_RULE: return dp = msg.datapath pkt = packet.Packet(msg.data) p_eth = pkt.get_protocols(ethernet.ethernet)[0] p_arp = pkt.get_protocols(arp.arp)[0] src_ip = p_arp.src_ip dst_ip = p_arp.dst_ip switch = self.nib.switch_for_dp(dp) in_port = msg.match["in_port"] # We only handle ARP requests for the virtual net here. All ARP replies and requests were # actually forwarded by l2_switch_handler, but no one will answer those for the virtual net. # We formulate ARP responses for those requests here. if p_arp.opcode == arp.ARP_REQUEST: # Translate virtual address to a real one real_dest_ip = self.nib.translate_ip(dst_ip, self.nib.actual_net_for(switch)) if real_dest_ip == None: pass elif self.nib.learned_ip(real_dest_ip): real_dest_mac = self.nib.mac_for_ip(real_dest_ip) OpenflowUtils.send_arp_reply(dp, in_port, p_eth.src, src_ip, real_dest_mac, dst_ip) else: # Send an ARP request to all ports, then just stay out of the way. If the host is up # on an unlearned port, it'll send a response, and that'll trigger learning. Then # when the NEXT ARP request for this address is received (it'll get retried a bunch of # times in practice), the reply can be generated from the ARP cache. # It doesn't matter so much where the ARP reply goes, because this switch will pick it up. switch_net = self.nib.actual_net_for(switch) src_ip = NetUtils.ip_for_network(switch_net, 2) OpenflowUtils.send_arp_request(dp, src_ip, real_dest_ip)
def ingress_src_dest_pairs_policy(self): policies = [] for src_dest_pair in self.nib.get_ingress_src_dest_pairs(): (src_ip, dst_ip) = src_dest_pair switch = self.nib.switch_for_ip(src_ip) dpid = self.nib.switch_to_dpid(switch) port = self.nib.port_for_ip(src_ip) src_host = NetUtils.host_of_ip(src_ip, self.nib.actual_net_for(switch)) # If this is going to the preferred network, write a rule choosing the # correct route here. opposite_switch = self.nib.opposite_switch(switch) if NetUtils.ip_in_network(dst_ip, self.nib.actual_net_for(opposite_switch)): # Get host from src_ip src_pref_net = self.nib.preferred_net(switch) new_src = NetUtils.ip_for_network(src_pref_net, src_host) dest_host = NetUtils.host_of_ip(dst_ip, self.nib.actual_net_for(opposite_switch)) new_dest = NetUtils.ip_for_network(self.nib.preferred_net(opposite_switch), dest_host) router_port = self.nib.router_port_for_switch(switch) output_actions = SetIP4Src(new_src) >> SetIP4Dst(new_dest) >> Send(router_port) policies.append( Filter(Policies.at_switch_port(dpid, port) & Policies.is_ip_from_to(src_ip, dst_ip)) >> output_actions ) else: # It's a direct path. Find the path first. for ap in self.nib.alternate_paths(): if NetUtils.ip_in_network(dst_ip, ap[opposite_switch]): alternate_path = ap new_src = NetUtils.ip_for_network(alternate_path[switch], src_host) router_port = self.nib.router_port_for_switch(switch) output_actions = SetIP4Src(new_src) >> Send(router_port) policies.append( Filter(Policies.at_switch_port(dpid, port) & Policies.is_ip_from_to(src_ip, dst_ip)) >> output_actions ) return Union(policies)
def packet_in(self, msg): dp = msg.datapath switch = self.nib.switch_for_dp(dp) ofproto = dp.ofproto parser = dp.ofproto_parser in_port = msg.match['in_port'] # Interesting packet data pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] dst = eth.dst src = eth.src # As stated before, we only learn from packets with IP source info in them. if not self.nib.learned(src) and (eth.ethertype == ether_types.ETH_TYPE_IP or eth.ethertype == ether_types.ETH_TYPE_ARP): if eth.ethertype == ether_types.ETH_TYPE_IP: p_ip = pkt.get_protocols(ipv4.ipv4)[0] src_ip = p_ip.src elif eth.ethertype == ether_types.ETH_TYPE_ARP: p_arp = pkt.get_protocols(arp.arp)[0] src_ip = p_arp.src_ip match_mac_src = parser.OFPMatch( vlan_vid = self.nib.vlan_for_switch(switch), eth_src = src ) match_mac_dst = parser.OFPMatch( vlan_vid = self.nib.vlan_for_switch(switch), eth_dst = src ) target_ip_net = self.nib.actual_net_for(switch) if src_ip == NetUtils.ip_for_network(target_ip_net, 1): # .1 is always the router # If this is coming from a router, we add goto table 2 rules in tables 0 and 1 instead. # All packets to/from the router go through table 2 to rewrite IP addresses instead. (See cross_campus_handler) OpenflowUtils.add_goto_table(dp, priority=0, match=match_mac_src, goto_table_id=2, table_id=0) OpenflowUtils.add_goto_table(dp, priority=0, match=match_mac_dst, goto_table_id=2, table_id=1) self.nib.learn(switch, self.nib.ROUTER_PORT, in_port, src, src_ip) # We also install path learning rules for table 3. Since this is the responsibility of # cross_campus_handler, it would be better if we installed them there, but I don't know quite # how to do it. # Incoming Packet Capture (e.g Coscin Ith->NYC on the Ith side), Cookie INCOMING_FLOW_RULE match = parser.OFPMatch( eth_dst = src, eth_type=ether_types.ETH_TYPE_IP ) actions = [ parser.OFPActionOutput(ofproto.OFPP_CONTROLLER) ] OpenflowUtils.add_flow(dp, priority=65535, match=match, actions=actions, table_id=3, cookie=CrossCampusHandler.INCOMING_FLOW_RULE) # Outgoing Packet Capture (e.g. Coscin Ith->NYC on the NYC side), Cookie OUTGOING_FLOW_RULE match = parser.OFPMatch( eth_src = src, eth_type=ether_types.ETH_TYPE_IP ) OpenflowUtils.add_flow(dp, priority=65534, match=match, actions=actions, table_id=3, cookie=CrossCampusHandler.OUTGOING_FLOW_RULE) # We don't learn any hosts until we've learned the router. Otherwise IP packets from the other # side of the switch might be learned as the router port. Send another request just in case it missed # the first one. elif self.nib.router_port_for_switch(switch) == None: self.arp_for_router(dp, switch) else: # The packet is from a host, not a router. A new packet carries information on the mac address, # which we turn into a GOTO-table 1 rule in table 0 OpenflowUtils.add_goto_table(dp, priority=65535, match=match_mac_src, goto_table_id=1, table_id=0) # And a send-to-port instruction for a destination match in table 1 actions = [ parser.OFPActionOutput(in_port) ] OpenflowUtils.add_flow(dp, priority=0, match=match_mac_dst, actions=actions, table_id=1) # Learn it for posterity self.nib.learn(switch, self.nib.ENDHOST_PORT, in_port, src, src_ip) # Don't send the packet out here if it's coming to/from a router. # Those will be handled by cross_campus_handler, which may rewrite IP's as well. cookie = msg.cookie if cookie == CrossCampusHandler.INCOMING_FLOW_RULE or cookie == CrossCampusHandler.OUTGOING_FLOW_RULE: return # Now we have to deal with the Mac destination. If we know it already, send the packet out that port. # Otherwise flood it. output_p = self.nib.port_for_mac(dst) if output_p == None: output_p = ofproto.OFPP_FLOOD out_data = msg.data if msg.buffer_id == 0xffffffff else None out = parser.OFPPacketOut(datapath=dp, buffer_id=msg.buffer_id, in_port=in_port, actions=[ parser.OFPActionOutput(output_p) ], data=out_data) dp.send_msg(out)
def dest_real_net(self, switch): net_and_mask = self.nib.actual_net_for(switch) (net, mask) = NetUtils.net_mask(net_and_mask) return IP4DstEq(net,mask)
def translate_ip(self, src_ip, new_net): current_net = self.coscin_net_for(src_ip) if current_net == None: return None src_host = NetUtils.host_of_ip(src_ip, current_net) return NetUtils.ip_for_network(new_net, src_host)
def router_ip_for_switch(self, switch): return NetUtils.ip_for_network(self.actual_net_for(switch), "1")
def subnet_for(self, ip): for sn in self.subnets: if NetUtils.ip_in_network(ip, sn.subnet_cidr): return sn return None
def gateway_ip(self, switch): return NetUtils.ip_for_network(self.nib.actual_net_for(switch), 1)