Search code examples
networkingmininetsdnryu

Best practices for avoiding unnecessary packet flooding in RYU-SDN controller when IP address of host is unknown?


I'm building a network using the RYU controller and Mininet simulator. When the controller receives a packet_in event, it looks up the IP address in the stored addresses. If it's not found, the packet is flooded through the network. However, this is inefficient because it could flood the network in vain if the target host isn't in the network.

One solution I've tried is having the hosts send dummy packets when the network starts to learn their IP addresses. But I'm wondering if there's a more professional or recommended approach to avoid flooding the network with unnecessary packets. Any suggestions or best practices for this scenario?


Solution

  • Before the packet itself is received at the controller in the packet_in event, an ARP request is received if the sender doesn't know the MAC address of the destination. The controller should respond before setting the flow of the packet itself. The scenario happens as follows:

    1. Host generates a packet but doesn't know the MAC address of the destination, so it sends an ARP request.
    2. The controller receives the ARP request and looks up the MAC address in its private ARP table.
    3. If the controller finds the MAC address, it sends an ARP reply to the host.
    4. If it doesn't find the MAC in the ARP table, it sends the ARP request to all the switches in the network.
    5. If the controller receives an ARP reply, it updates the ARP table, then sends an ARP reply to the original request sender.
    6. When the host receives the ARP reply, it sends the packet to the switch, which in turn sends it to the controller for it to set up a flow.
    7. If no ARP reply is received, the host times out, no packets are sent, and the network is no longer busy.

    Remember not to flood the network as long as you can.

    Example code:

    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
    
        pkt = packet.Packet(ev.msg.data)
        eth = pkt.get_protocol(ethernet.ethernet)
    
        # avoid broadcast from LLDP
        if eth.ethertype == 35020:
            return
    
        # drop the IPV6 Packets if not using IPV6
        if pkt.get_protocol(ipv6.ipv6):
            match = ev.msg.datapath.ofproto_parser.OFPMatch(eth_type=eth.ethertype)
            actions = []
            self.add_flow(ev.msg.datapath, 1, match, actions)
            return 
    
        arp_pkt = pkt.get_protocol(arp.arp)
    
        if arp_pkt:
            # ARP packet is received
            
            # Store the sender host information
            src_ip = arp_pkt.src_ip
            src_mac = arp_pkt.src_mac
            if src_ip not in self.arp_table:
                self.arp_table[src_ip] = src_mac
    
            dst_ip = arp_pkt.dst_ip
    
            if arp_pkt.opcode == arp.ARP_REQUEST:
                if dst_ip in hosts_by_ip:
                     # If ARP request and destination MAC is known, send ARP reply
                    print("ARP destination is in hosts, sending ARP reply")
                    dst_mac = self.arp_table[dst_ip]
                    self.send_arp_reply(ev.msg.datapath,
                                        dst_mac, src_mac,  dst_ip, src_ip,  ev.msg.match['in_port'])
                else:
                    # If ARP request and destination MAC is not known, send ARP request to all hosts
                    print("ARP destination is not in hosts, sending ARP request")
                    for dp in self.datapaths.values():
                        self.send_arp_request(dp, src_mac, src_ip, dst_ip)
    
            else:
                print("ARP reply received from ", src_ip)
                # No need to do anything as we already stored the host IP, we wait for the requester to send another ARP request
        else:
            # IPv4 packet received because no flow was found in the switch flow table
            # TODO: set the flow (in the switch) here
    
    def send_arp_reply(self, datapath, src_mac, dst_mac, src_ip, dst_ip, in_port):
        self.send_arp(datapath, arp.ARP_REPLY, src_mac,
                        src_ip, dst_mac, dst_ip, in_port)
    
    def send_arp_request(self, datapath, src_mac, src_ip, dst_ip):
        self.send_arp(datapath, arp.ARP_REQUEST, src_mac,
                        src_ip, None, dst_ip, None)
    
    def send_arp(self, datapath, opcode, src_mac, src_ip, dst_mac, dst_ip, in_port):
        eth_dst_mac = dst_mac
        arp_dst_mac = dst_mac
        actions = [datapath.ofproto_parser.OFPActionOutput(in_port)]
    
        if opcode == arp.ARP_REQUEST:
            eth_dst_mac = 'ff:ff:ff:ff:ff:ff'
            arp_dst_mac = '00:00:00:00:00:00'
            actions = [datapath.ofproto_parser.OFPActionOutput(
                datapath.ofproto.OFPP_FLOOD)]
    
        # Create Ethernet header
        eth = ethernet.ethernet(
            dst=eth_dst_mac,
            src=src_mac,
            ethertype=packet.ethernet.ether.ETH_TYPE_ARP
        )
    
        # Create ARP header
        arp_header = arp.arp(
            opcode=opcode,
            src_mac=src_mac,
            src_ip=src_ip,
            dst_mac=arp_dst_mac,
            dst_ip=dst_ip
        )
    
        # Create packet and send it
        pkt = packet.Packet()
        pkt.add_protocol(eth)
        pkt.add_protocol(arp_header)
    
        pkt.serialize()
        datapath.send_packet_out(
            buffer_id=datapath.ofproto.OFP_NO_BUFFER,
            in_port=datapath.ofproto.OFPP_CONTROLLER,
            actions=actions,
            data=pkt.data
        )