def handle_switch_up(self, ev): dp = ev.msg.datapath ofp_parser = dp.ofproto_parser ofp = dp.ofproto self.logger.info("Switch "+str(dp.id)+" says hello.") switch = self.nib.save_switch(dp) self.logger.info("Connected to Switch: "+self.nib.switch_description(dp)) if self.mc != None: # This will block until the controller actually becomes a primary self.mc.handle_datapath(ev) # Start background thread to monitor switch-to-controller heartbeat self.last_heartbeat = time.time() if not self.heartbeat_monitor_started: thread.start_new_thread( self.heartbeat_monitor, (self, ) ) self.heartbeat_monitor_started = True OpenflowUtils.delete_all_rules(dp) OpenflowUtils.send_table_miss_config(dp) self.l2_learning_switch_handler.install_fixed_rules(dp) self.cross_campus_handler.install_fixed_rules(dp) self.arp_handler.install_fixed_rules(dp) self.path_selection_handler.install_fixed_rules(dp)
def write_table_2_rule(self, switch, dp, ip, pkt, actions, direction ): parser = dp.ofproto_parser if ip.proto == in_proto.IPPROTO_TCP: tcp_pkt = pkt.get_protocols(tcp.tcp)[0] match = parser.OFPMatch( ipv4_src=ip.src ,ipv4_dst=ip.dst ,eth_type=ether_types.ETH_TYPE_IP ,ip_proto=ip.proto ,vlan_vid=self.nib.vlan_for_switch(switch) ,tcp_src = tcp_pkt.src_port ,tcp_dst = tcp_pkt.dst_port ) src_port = tcp_pkt.src_port dst_port = tcp_pkt.dst_port elif ip.proto == in_proto.IPPROTO_UDP: udp_pkt = pkt.get_protocols(udp.udp)[0] match = parser.OFPMatch( ipv4_src=ip.src ,ipv4_dst=ip.dst ,eth_type=ether_types.ETH_TYPE_IP ,ip_proto=ip.proto ,vlan_vid=self.nib.vlan_for_switch(switch) ,udp_src = udp_pkt.src_port ,udp_dst = udp_pkt.dst_port ) src_port = udp_pkt.src_port dst_port = udp_pkt.dst_port # The flow will naturally age out after 10 minutes of idleness. That way we can pick a new path for # it if it starts up again. OpenflowUtils.add_flow(dp, priority=0, match=match, actions=actions, table_id=2, idle_timeout=self.IDLE_TIMEOUT) self.logger.info("Added "+direction+" hash rule for "+str(ip.src)+":" + str(src_port) + " -> "+ str(ip.dst) +":" + str(dst_port) )
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 install_fixed_rules(self, dp): # We grab all ARP requests and replies. You can't match with any more granularity # than that on HP's custom pipleine, unfortunately. ofproto = dp.ofproto parser = dp.ofproto_parser match_arp = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_ARP) actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER)] OpenflowUtils.add_flow(dp, priority=50, match=match_arp, actions=actions, table_id=3, cookie=self.ARP_RULE)
def handle_switch_up(self, ev): dp = ev.msg.datapath parser = dp.ofproto_parser ofproto = dp.ofproto switch = self.nib.save_switch(dp) self.logger.info("Connected to Switch: "+self.nib.switch_description(dp)) # This will block until the controller actually becomes a primary self.mc.handle_datapath(ev) # Start background thread to monitor switch-to-controller heartbeat self.last_heartbeat = time.time() if not self.heartbeat_monitor_started: thread.start_new_thread( self.heartbeat_monitor, (self, ) ) self.heartbeat_monitor_started = True OpenflowUtils.delete_all_rules(dp) OpenflowUtils.send_table_miss_config(dp) # Table 0, the Source Mac table, has table miss=Goto Controller so it can learn # newly-connected macs match_all = parser.OFPMatch() actions = [ parser.OFPActionOutput(ofproto.OFPP_CONTROLLER) ] OpenflowUtils.add_flow(dp, priority=0, match=match_all, actions=actions, table_id=0, cookie=self.LEARN_NEW_MACS_RULE) # Table 1, the Dest Mac table, has table-miss = Flood. If the destination exists, # it'll send a reply packet that'll get learned in table 0. We can afford to do a # sloppy non-spanning-tree flood because there's only one switch in our topology actions = [ parser.OFPActionOutput(ofproto.OFPP_FLOOD) ] OpenflowUtils.add_flow(dp, priority=0, match=match_all, actions=actions, table_id=1)
def install_fixed_rules(self, dp): # Table 0, the Source Mac table, has table miss=Goto Controller so it can learn # newly-connected macs switch = self.nib.switch_for_dp(dp) ofproto = dp.ofproto parser = dp.ofproto_parser match_all = parser.OFPMatch() actions = [ parser.OFPActionOutput(ofproto.OFPP_CONTROLLER) ] OpenflowUtils.add_flow(dp, priority=0, match=match_all, actions=actions, table_id=0, cookie=self.LEARN_NEW_MACS_RULE) # Table 1, the Dest Mac table, has table-miss = Flood. If the destination exists, # it'll send a reply packet that'll get learned in table 0. We can afford to do a # sloppy non-spanning-tree flood because there's only one switch in our topology actions = [ parser.OFPActionOutput(ofproto.OFPP_FLOOD) ] OpenflowUtils.add_flow(dp, priority=0, match=match_all, actions=actions, table_id=1) self.arp_for_router(dp, switch)
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 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 handle_packet_in(self, ev): # The HP considers a successful Packet In as a probe, so we reset the heartbeat here as well self.last_heartbeat = time.time() msg = ev.msg 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 if not self.nib.learned(src): 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 ) 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, "0.0.0.0") # 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 = parser.OFPPacketOut(datapath=dp, buffer_id=msg.buffer_id, in_port=in_port, actions=[ parser.OFPActionOutput(output_p) ], data=msg.data) dp.send_msg(out)
def write_table_2_rule(self, switch, dp, ip, pkt, actions, direction): parser = dp.ofproto_parser if ip.proto == in_proto.IPPROTO_TCP: tcp_pkt = pkt.get_protocols(tcp.tcp)[0] match = parser.OFPMatch(ipv4_src=ip.src, ipv4_dst=ip.dst, eth_type=ether_types.ETH_TYPE_IP, ip_proto=ip.proto, vlan_vid=self.nib.vlan_for_switch(switch), tcp_src=tcp_pkt.src_port, tcp_dst=tcp_pkt.dst_port) src_port = tcp_pkt.src_port dst_port = tcp_pkt.dst_port elif ip.proto == in_proto.IPPROTO_UDP: udp_pkt = pkt.get_protocols(udp.udp)[0] match = parser.OFPMatch(ipv4_src=ip.src, ipv4_dst=ip.dst, eth_type=ether_types.ETH_TYPE_IP, ip_proto=ip.proto, vlan_vid=self.nib.vlan_for_switch(switch), udp_src=udp_pkt.src_port, udp_dst=udp_pkt.dst_port) src_port = udp_pkt.src_port dst_port = udp_pkt.dst_port # The flow will naturally age out after 10 minutes of idleness. That way we can pick a new path for # it if it starts up again. OpenflowUtils.add_flow(dp, priority=0, match=match, actions=actions, table_id=2, idle_timeout=self.IDLE_TIMEOUT) self.logger.info("Added " + direction + " hash rule for " + str(ip.src) + ":" + str(src_port) + " -> " + str(ip.dst) + ":" + str(dst_port))
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)