Hi Srini,

Sorry I should of been a more specific.

I obtained my copy of nox via the following git commands:

git checkout -b destiny origin/destiny

Is this correct?

The problem came up when I tried to start nox (NOX 0.8.0~full~beta) with the discovery component active in verbose mode. I got the following error message:

Received LLDP packet from unconnected switch

When all my openflow-enabled switches had connected to the controller. The problem seemed to come from the fact that the switch reports the datapathid (as specified in the spec) as a sequence of 7 bytes with the most significant byte containing the vlan id of the openflow instance on the the switch, which was not the case of the openflow-0.8.9 HP firmware.  

The discovery module takes takes these steps to discover connections:

1. Creates a lldp packet with the 6 least significant bytes of the dpid as a tlv (see line 65 and 66 of discovery.py).  
2. The received lldp packet's tlv field is then used to compute a chassis id (line 276), this chassis id is only 6 bytes long
3. The chassis id is then checked against self.dps (line 285), which contains the dpids of the connected switches. Since the chassis id and the dpid are of different lengths, the only way this would work is if the dpid started with a sequence of four zeros (which was the case for me under the previous HP openflow firmware, luckily). Anyway, this test clearly fails, the above error message is returned and no connections are discovered.  

Hope this is clear enough. If not, don't hesitate to contact me again.

Also, I noticed that the switchstats.py in the switchstats component has the following call:

self.ctxt.send_port_stats_request(dp)

Shouldn't this call be:

self.ctxt.send_port_stats_request(dp, openflow.OFPP_NONE)



Thanks, 

Ali
# Copyright 2008 (C) Nicira, Inc.
# 
# This file is part of NOX.
# 
# NOX is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# NOX is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with NOX.  If not, see <http://www.gnu.org/licenses/>.



from nox.lib.core import *

import logging

from nox.netapps.discovery.pylinkevent   import Link_event
from nox.netapps.bindings_storage.pybindings_storage import pybindings_storage
from nox.netapps.user_event_log.pyuser_event_log import pyuser_event_log,LogLevel
from nox.lib.packet.packet_utils      import array_to_octstr
from nox.lib.packet.packet_utils      import longlong_to_octstr
from nox.lib.packet.ethernet          import LLDP_MULTICAST, NDP_MULTICAST
from nox.lib.packet.ethernet          import ethernet  
from nox.lib.packet.lldp              import lldp, chassis_id, port_id, end_tlv
from nox.lib.packet.lldp              import ttl 
from nox.lib.netinet.netinet          import create_datapathid_from_host
from nox.lib.openflow                 import OFPP_LOCAL


import struct
import array
import socket
import time
import copy

LLDP_TTL             = 120 # currently ignored
LLDP_SEND_PERIOD     = .10 
TIMEOUT_CHECK_PERIOD = 5.
LINK_TIMEOUT         = 10.

lg = logging.getLogger('discovery')

# ---------------------------------------------------------------------- 
#  Utility function to create an lldp packet given a chassid
# 
#  Create lldp packet using the least significant 48 bits of dpid for
#  chassis ID 
# 
# ---------------------------------------------------------------------- 
  
def create_discovery_packet(dpid, portno, ttl_):    

    # Create lldp packet
    discovery_packet = lldp()
    cid = chassis_id()

    # nbo 
    cid.fill(4, array.array('B', struct.pack('!Q',dpid))[2:8])
    discovery_packet.add_tlv(cid)

    pid = port_id()
    pid.fill(2,array.array('B',struct.pack('!H', portno)))
    discovery_packet.add_tlv(pid)

    ttlv = ttl()
    ttlv.fill(ttl_)
    discovery_packet.add_tlv(ttlv)

    discovery_packet.add_tlv(end_tlv())

    eth = ethernet()
    # To insure that the LLDP src mac address is not a multicast
    # address, since we have no control on choice of dpid
    eth.src = '\x00' + struct.pack('!Q',dpid)[3:8]
    eth.dst = NDP_MULTICAST
    eth.set_payload(discovery_packet)
    eth.type = ethernet.LLDP_TYPE

    return eth

## \ingroup noxcomponents
# LLDP discovery application for topology inference
#
# This application handles generation and parsing/interpreting LLDP
# packets for all switches on the network. The bulk of the functionality
# is performed in the following handlers:
# <ul>
# <li>send_lldp() : this is a generator function which is called in a timer
# every timeout period.  It iterates over all ports on the network and
# sends an LLDP packet on each invocation (note: that is exactly *one*
# packet per timeout period).  The LLDP packets contain the chassis ID,
# and the port number of the outgoing switch/port
#
# <li>lldp_input_handler() : packet_in handler called on receipt of an LLDP
# packet. This infers the link-level connectivity by querying the LLDP
# packets.  The network links are stored in the instance variable
# adjacency_list
#
# <li>timeout_links() : periodically iterates over the discovered links on
# the network and detects timeouts.  Timeouts update the global view and
# generate a node changed event
# </ul>
#
# <b>Inferring links:</b><br>
# <br>
# Each LLDP packet contains the sending port and datapath ID.  The
# datapath is currently encoded as a 48bit MAC in the chassis ID TLV,
# hence the lower 16bits are always 0. 
#
# <b>Shortcomings:</b>
#
# The fundamental problem with a centralized approach to topology
# discovery is that all ports must be scanned linearly which greatly
# reduces response time. 
#
# This should really be implemented on the switch

class discovery(Component):

    def __init__(self, ctxt):
        Component.__init__(self, ctxt)

        self._bindings       = self.resolve(pybindings_storage)
        self._user_event_log = self.resolve(pyuser_event_log)

        self.dps            = {}
        self.lldp_packets   = {}
        self.adjacency_list = {}

    def configure(self, configuration):

        arg_len = len(configuration['arguments'])


        self.lldp_send_period = LLDP_SEND_PERIOD
        if arg_len == 1:
            try:
                val = float(configuration['arguments'][0])
                self.lldp_send_period = val;
                lg.debug("Setting LLDP send timer to " + str(val))
            except Exception, e:
                lg.error("unable to convert arg to float " + configuration['arguments'][0])

        self.register_event(Link_event.static_get_name())
        Link_event.register_event_converter(self.ctxt)

    def install(self):
        self.register_for_datapath_join ( lambda dp,stats : discovery.dp_join(self, dp, stats) )
        self.register_for_datapath_leave( lambda dp       : discovery.dp_leave(self, dp) )
        self.register_for_port_status( lambda dp, reason, port : discovery.port_status_change(self, dp, reason, port) )
        # register handler for all LLDP packets 
        match = {DL_DST : array_to_octstr(array.array('B',NDP_MULTICAST)),\
                  DL_TYPE: ethernet.LLDP_TYPE}
        self.register_for_packet_match(lambda
            dp,inport,reason,len,bid,packet :
            discovery.lldp_input_handler(self,dp,inport,reason,len,bid,packet),
            0xffff, match)

        self.start_lldp_timer_thread()

    def getInterface(self):
        return str(discovery)

    # --
    # On datapath join, create a new LLDP packet per port 
    # --

    def dp_join(self, dp, stats): 
        self.dps[dp] = stats
  
        self.lldp_packets[dp]  = {}
        for port in stats[PORTS]:
            if port[PORT_NO] == OFPP_LOCAL:
                continue
            self.lldp_packets[dp][port[PORT_NO]] = create_discovery_packet(dp, port[PORT_NO], LLDP_TTL);

    # --
    # On datapath leave, delete all associated links
    # --

    def dp_leave(self, dp): 
    
        if dp in self.dps:
            del self.dps[dp]  
        if dp in self.lldp_packets:
            del self.lldp_packets[dp]  
    
        deleteme = []
        for linktuple in self.adjacency_list:
            if linktuple[0] == dp or linktuple[2] == dp: 
                deleteme.append(linktuple)
    
        self.delete_links(deleteme)


    # --
    # Update the list of LLDP packets if ports are added/removed
    # --

    def port_status_change(self, dp, reason, port):
        '''Update LLDP packets on port status changes

        Add to the list of LLDP packets if a port is added.
        Delete from the list of LLDP packets if a port is removed.

        Keyword arguments:
        dp -- Datapath ID of port
        reason -- what event occured
        port -- port
        '''
        # Only process 'sane' ports
        if port[PORT_NO] <= openflow.OFPP_MAX:
            if reason == openflow.OFPPR_ADD:
                self.lldp_packets[dp][port[PORT_NO]] = create_discovery_packet(dp, port[PORT_NO], LLDP_TTL);
            elif reason == openflow.OFPPR_DELETE:
                del self.lldp_packets[dp][port[PORT_NO]]

        return CONTINUE


    def timeout_links(self):

      curtime = time.time()
      self.post_callback(TIMEOUT_CHECK_PERIOD, lambda : discovery.timeout_links(self))

      deleteme = []
      for linktuple in self.adjacency_list:
          if (curtime - self.adjacency_list[linktuple]) > LINK_TIMEOUT:
              deleteme.append(linktuple)
              lg.warn('link timeout ('+longlong_to_octstr(linktuple[0])+" p:"\
                         +str(linktuple[1]) +' -> '+\
                         longlong_to_octstr(linktuple[2])+\
                         'p:'+str(linktuple[3])+')')

      self.delete_links(deleteme)

    # ---------------------------------------------------------------------- 
    #  Handle incoming lldp packets.  Use to maintain link state
    # ---------------------------------------------------------------------- 

    def lldp_input_handler(self, dp_id, inport, ofp_reason, total_frame_len, buffer_id, packet):
        
        assert (packet.type == ethernet.LLDP_TYPE)
    
        if not packet.next:
            lg.error("lldp_input_handler lldp packet could not be parsed")
            return
    
        assert (isinstance(packet.next, lldp))
    
        lldph = packet.next
        if  (len(lldph.tlvs) < 4) or \
            (lldph.tlvs[0].type != lldp.CHASSIS_ID_TLV) or\
            (lldph.tlvs[1].type != lldp.PORT_ID_TLV) or\
            (lldph.tlvs[2].type != lldp.TTL_TLV):
            lg.error("lldp_input_handler invalid lldp packet")
            return

        # parse out chassis id 
        if lldph.tlvs[0].subtype != chassis_id.SUB_MAC:
            lg.error("lldp chassis ID subtype is not MAC, ignoring")
            return
        assert(len(lldph.tlvs[0].id) == 6)
        cid = lldph.tlvs[0].id
    
        # convert 48bit cid (in nbo) to longlong
        chassid = cid[5]       | cid[4] << 8  | \
                  cid[3] << 16 | cid[2] << 24 |  \
                  cid[1] << 32 | cid[0] << 40 

        # if chassid is from a switch we're not connected to, ignore
        if chassid not in self.dps:
            lg.debug('Recieved LLDP packet from unconnected switch')
            return
    
        # grab 16bit port ID from port tlv
        if lldph.tlvs[1].subtype != port_id.SUB_PORT:
            return # not one of ours
        if len(lldph.tlvs[1].id) != 2:
            lg.error("invalid lldph port_id format")
            return
        (portid,)  =  struct.unpack("!H", lldph.tlvs[1].id)

        if (dp_id, inport) == (chassid, portid):
            lg.error('Loop detected, received our own LLDP event')
            return

        # print 'LLDP packet in from',longlong_to_octstr(chassid),' port',str(portid)
    
        linktuple = (dp_id, inport, chassid, portid)
    
        if linktuple not in self.adjacency_list:
            self.add_link(linktuple)
            lg.warn('new link detected ('+longlong_to_octstr(linktuple[0])+' p:'\
                       +str(linktuple[1]) +' -> '+\
                       longlong_to_octstr(linktuple[2])+\
                       ' p:'+str(linktuple[3])+')')
    
    
        # add to adjaceny list or update timestamp
        self.adjacency_list[(dp_id, inport, chassid, portid)] = time.time()

    # ---------------------------------------------------------------------- 
    # Start LLDP timer which sends an LLDP packet every
    # LLDP_SEND_PEROID.
    # ---------------------------------------------------------------------- 

    def start_lldp_timer_thread(self):

        #---------------------------------------------------------------------- 
        # Generator which iterates over a set of dp's and sends an LLDP packet
        # out of each port.  
        #---------------------------------------------------------------------- 
        
        def send_lldp (packets):
            for dp in packets:
                # if they've left, ignore
                if not dp in self.dps:
                    continue
                try:    
                    for port in packets[dp]:
                        #print 'Sending packet out of ',longlong_to_octstr(dp), ' port ',str(port)
                        self.send_openflow_packet(dp, packets[dp][port].tostring(), port)
                        yield dp 
                except Exception, e:
                    # catch exception while yielding
                    lg.error('Caught exception while yielding'+str(e))
        
        def build_lldp_generator():
            
            def g():
                try:
                    g.sendfunc.next()
                except StopIteration, e:    
                    g.sendfunc = send_lldp(copy.deepcopy(self.lldp_packets))
                except Exception, e:    
                    lg.error('Caught exception from generator '+str(e))
                    g.sendfunc = send_lldp(copy.deepcopy(self.lldp_packets))
                self.post_callback(self.lldp_send_period, g)
            g.sendfunc = send_lldp(copy.deepcopy(self.lldp_packets))
            return g

        self.post_callback(self.lldp_send_period, build_lldp_generator())
        self.post_callback(TIMEOUT_CHECK_PERIOD, lambda : discovery.timeout_links(self))

    def post_link_event(self, linktuple, action):
        e = Link_event(create_datapathid_from_host(linktuple[0]),
                      create_datapathid_from_host(linktuple[2]),
                      linktuple[1], linktuple[3], action)
        self.post(e)

    #---------------------------------------------------------------------- 
    #  Link addition/deletion
    #---------------------------------------------------------------------- 

    def add_link(self, linktuple):            
        self.post_link_event(linktuple, Link_event.ADD)
        src_dpid = create_datapathid_from_host(linktuple[0])
        src_port = linktuple[1]
        dst_dpid = create_datapathid_from_host(linktuple[2])
        dst_port = linktuple[3]
        self._bindings.add_link(src_dpid,src_port,dst_dpid,dst_port)
        self._user_event_log.log("discovery",LogLevel.ALERT, 
            "Added network link between {sl} and {dl}",
            set_src_loc = (src_dpid,src_port), 
            set_dst_loc = (dst_dpid,dst_port)) 

    def delete_links(self, deleteme):

        for linktuple in deleteme:                    
            del self.adjacency_list[linktuple]
            src_dpid = create_datapathid_from_host(linktuple[0])
            src_port = linktuple[1]
            dst_dpid = create_datapathid_from_host(linktuple[2])
            dst_port = linktuple[3]
            try:
                self._bindings.remove_link(src_dpid,src_port,dst_dpid,dst_port)
                self._user_event_log.log("discovery",LogLevel.ALERT, 
                  "Removed network link between {sl} and {dl}", 
                  set_src_loc = (src_dpid,src_port), 
                  set_dst_loc = (dst_dpid,dst_port)) 
            except Exception, e:        
              lg.error('Error removing links from binding storage')
                
            self.post_link_event(linktuple, Link_event.REMOVE)

    #---------------------------------------------------------------------- 
    #   Public API
    #---------------------------------------------------------------------- 

    def is_switch_only_port(self, dpid, port):
        """ Returns True if (dpid, port) designates a port that has any
        neighbor switches"""
        for dp1, port1, dp2, port2 in self.adjacency_list:
            if dp1 == dpid and port1 == port:
                return True
            if dp2 == dpid and port2 == port:
                return True
        return False


def getFactory():
    class Factory:
        def instance(self, ctxt):
            return discovery(ctxt)

    return Factory()

On Sep 4, 2010, at 12:15 AM, Srini Seetharaman wrote:

Hi Ali
Could you please let us know what you meant by "does not work"?

Is the HP switch dropping the LLDP packet that is being packet-out?
The only problem we used to have with HP switches was that if the LLPD
src MAC was a multicast address, then the switch would drop it. This
has been fixed for the past few months in most of the branches. So,
it'll help us to know what version of NOX you're running (If you send
us the original discovery.py file, that helps too).

Thanks
Srini.

On Fri, Sep 3, 2010 at 10:31 AM, Ali Al-Shabibi <ali.al-shab...@cern.ch> wrote:
Hi all,
I noticed that the discovery module in the destiny branch does not work with
HP switches (at least). So attached is a patch that resolves this situation.
I am not entirely sure this is the appropriate way to solve the problem but
it works for me.
Hope it helps,
Ali Al-Shabibi
Doctoral Student
PH-ATD
CERN - European Organization for Nuclear Research
Office: 513 R-018
ali.al-shab...@cern.ch
Tel :   +41 22 767 86 46

_______________________________________________
nox-dev mailing list
nox-dev@noxrepo.org
http://noxrepo.org/mailman/listinfo/nox-dev_noxrepo.org



Ali Al-Shabibi
Doctoral Student
PH-ATD
CERN - European Organization for Nuclear Research
Office: 513 R-018
ali.al-shab...@cern.ch
Tel :   +41 22 767 86 46

_______________________________________________
nox-dev mailing list
nox-dev@noxrepo.org
http://noxrepo.org/mailman/listinfo/nox-dev_noxrepo.org

Reply via email to