Hi Min-Hyup,

I was looking into something really similar a while back, and did
write a controller that would do something similar. The controller
was intended mainly for testing.
I tried to remove as much of the special code as I could. I don't think
the code will compile as it is now, but it might give you an idea
of how you can go about implementing this.

I have been trying various things with VLANs, so if you have any
further questions I might be able to help.

--niky


Min-Hyup KANG wrote:
>
> Hi all,
>
>
> I am currently having several Pronto 3290 switches.
>
>
> so, I would link to devide and assign some VLAN , such as VLAN2, VLAN3
> for some port.
>
>
> I believe that I should write some code about vlan in NOX controller.
>
> but, It is not easy for me. so I need your help.
>
>
> Can you explain easily how to write some code for vlan assignment.
>
> or
>
> please let me know example code showing vlan assignment.
>
>
> Any suggestion would help to me.
>
>
> and I am looking forward response.
>
>
> Thanks,
>
>
>
>
> Best Regards,
> Min-Hyup KANG
>
> <mailto:kang-min-h...@hanmail.net>
> ------------------------------------------------------------------------
>
> _______________________________________________
> nox-dev mailing list
> nox-dev@noxrepo.org
> http://noxrepo.org/mailman/listinfo/nox-dev
>   

#Copyright (c) 2010 Raytheon BBN Technologies
#
#
#Permission is hereby granted, free of charge, to any person obtaining
#
#a copy of this software and/or hardware specification (the "Work") to
#
#deal in the Work without restriction, including without limitation the
#
#rights to use, copy, modify, merge, publish, distribute, sublicense,
#
#and/or sell copies of the Work, and to permit persons to whom the Work
#
#is furnished to do so, subject to the following conditions:
#
#
#
#The above copyright notice and this permission notice shall be
#
#included in all copies or substantial portions of the Work.
#
#
#
#THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
#
#OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
#
#MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
#
#NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
#
#HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
#
#WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#
#OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS
#
#IN THE WORK.

# This file is a modification of the discovery module of NOX. 
# This file also needs to be part of NOX in order to be compiled and used
# 
# The release/copyright for the NOX software follows. 
#
# Copyright 2008 (C) Nicira, Inc.
# 
# 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/>.
#

import nox.lib.openflow as openflow

from nox.lib.core     import *

from nox.lib.packet.ethernet     import ethernet
from nox.lib.packet.vlan import vlan
from nox.lib.packet.ipv4	 import ipv4
from nox.lib.packet.packet_utils import mac_to_str, mac_to_int, ethtype_to_str

from twisted.python import log

from nox.lib.packet.packet_utils import  * 

import logging
from time import time
from socket import htons
from struct import unpack

logger = logging.getLogger('nox.coreapps.examples.vlanswitch')

# Global vlanswitch instance 
inst = None

# The actual physical port number of a switch doesn't necessarily correspond to the 
# port number that is advertized and passed along with the packets. It seems that the 
# physical port is what corresponds to the name field of the stats that are advertized 
# when a switch registers, keep a map between port names and numbers so that we can
# do the switching
port_name2no_map = {}
port_no2name_map = {}

# Timeout for cached MAC entries
IDLE_CACHE_TIMEOUT = 5
HARD_CACHE_TIMEOUT = 60

#Switch statistics
switch_stats = {}

vlan_conf = { 
              # <SWITCH_DPID> should be the dpid of the switch that this
              # configuration corresponds to
              <SWITCH_DPID> : {
                  "port_to_vlans" : {
                                  '1' : ('a', [1714]),
                                  '2' : ('a', [1714]),
                                  '47' : ('t', [1713, 1714]),
                                 },
              },
            }
                

   
# --
# If we've learned the destination MAC set up a flow and
# send only out of its inport.  Else, flood.
# also check whether there is a need to set the vlan on the 
# packet
# --

def forward_packet(dpid, inport, packet, buf, bufid):    
  global inst, port_name2no_map, port_no2name_map

  if not port_name2no_map.has_key(dpid) or not port_no2name_map.has_key(dpid) : 
    logger.warn("DPID %x doesn't have entry in the port maps" % dpid)
    return
  if not port_no2name_map[dpid].has_key(inport) :
      logger.warn('Pkt came from unregistered port %d. Drop packet!' % inport)
      # We are dropping the packet so return true since a decision about routing 
      # has been made
      return 
  inport_name = port_no2name_map[dpid][inport]
  logger.debug('Inport %s' % inport_name)

  if not vlan_conf.has_key(dpid) :
    logger.info("Don't have vlan conf for DPID 0x%x, do norma l2 forwarding" % dpid)
    forward_l2_packet(dpid, inport, packet, buf, bufid)
    return 

  if not vlan_conf[dpid]['port_to_vlans'].has_key(inport_name) :
    logger.warn("DPID %x doesn't have vlan configuration for port %s. Drop packet" % (dpid, inport_name))
    return

  dstaddr = packet.dst.tostring()
  actions = []

  inport_vlan_conf = vlan_conf[dpid]['port_to_vlans'][inport_name]
  
  # Initialize the packet vlan to -1 
  pkt_vlan_tag = -1
  # Find out if this is packet is taged and print the vlan information
  if packet.type == ethernet.VLAN_TYPE :
    if inport_vlan_conf[0] != 't' :
      logger.warn("VLAN tagged packet received on a non trunk port!  Drop packet" )
      return
    vlanh = packet.find('vlan')
    if vlanh == None : 
      logger.warn("Packet type is VLAN_TYPE, but no vlan headers are present!  Drop packet" )
      return
    pkt_vlan_tag = vlanh.id
    # Keep track of dl_type
    pkt_dl_type = vlanh.eth_type
    logger.debug('vlan headers %s' % str(vlanh))
  else : 
    if inport_vlan_conf[0] != 'a' :
      logger.warn("Untagged packet received on a non access port!  Drop packet" )
      return
    logger.debug('packet is not tagged' )
    pkt_vlan_tag = inport_vlan_conf[1][0]
    pkt_dl_type = packet.type
  

  # <GET_OUTPORT> : A function that will return the outport for this packet, the
  # outport is the OpenFlow outport, i.e. not the  port name
  outport = <GET_OUTPORT>

  # if we are just broadcasting then just send this packet
  if outport == openflow.OFPP_FLOOD : 
    logger.debug("Packet should be flooded")

    flood_packet(dpid, inport, packet, buf, bufid, pkt_vlan_tag)    
    return 

  # if we have a specific port, install a flow
  else : 
    # Get the outport configuration 
    if not port_no2name_map[dpid].has_key(outport) :
      logger.warn('Pkt for unregistered port %d. Drop packet!' % outport)
      return 
    outport_name = port_no2name_map[dpid][outport]
    logger.debug('Outport %s' % outport_name)

    outport_vlan_conf = vlan_conf[dpid]['port_to_vlans'][outport_name]
    # If vlan of the packet is not in the port configuration 
    if pkt_vlan_tag not in outport_vlan_conf[1] :
      logger.warn('Vlan of outport(%s), does not match pkt vlan (%d)'
                       % (str(outport_vlan_conf), pkt_vlan_tad))
      #drop packet
      return
    if outport_vlan_conf[0] == 'a' and packet.type == ethernet.VLAN_TYPE : 
      # if the packet is tagged and the output port is access, remove the tag
        actions.append([openflow.OFPAT_STRIP_VLAN])
    elif outport_vlan_conf[0] == 't' and packet.type != ethernet.VLAN_TYPE : 
      # if the packet is not tagged and the output port is trunk, add the tag
      actions.append([openflow.OFPAT_SET_VLAN_VID, pkt_vlan_tag])

    # Add the action of sending the packet to the computed outport
    actions.append([openflow.OFPAT_OUTPUT, [0, outport]])
    logger.debug("ACTIONS : %s" % str(actions))

    flow = extract_flow(packet)
    logger.info("%s : Install flow %s" % (str(time()), str(flow)))
    inst.install_datapath_flow(dpid, flow, IDLE_CACHE_TIMEOUT, 
               HARD_CACHE_TIMEOUT, actions,
               bufid, openflow.OFP_DEFAULT_PRIORITY,
               inport, buf)

  return

def flood_packet(dpid, inport, packet, buf, bufid, pkt_vlan_tag) :
  (access,trunk) = get_active_ports_on_vlan(dpid, pkt_vlan_tag)
  # remove inport
  access = set(access) - set([inport])
  trunk = set(trunk) - set([inport])

  access_actions = []
  trunk_actions = []
  # If the packet is tagged strip the tag for access ports 
  if packet.type == ethernet.VLAN_TYPE : 
    access_actions.append([openflow.OFPAT_STRIP_VLAN])
  # If the packet is not tagged add a tag for trunk ports 
  else :
    trunk_actions.append([openflow.OFPAT_SET_VLAN_VID, pkt_vlan_tag])

  flow = extract_flow(packet)
  if len(access) > 0 :
    for p in access : 
      access_actions.append([openflow.OFPAT_OUTPUT, [0, p]])
    logger.debug("Flooding packet in access ports, actions %s" 
                   % str(access_actions))

    inst.install_datapath_flow(dpid, flow, IDLE_CACHE_TIMEOUT, 
                                       openflow.OFP_FLOW_PERMANENT,
                                       access_actions,
                                       bufid, openflow.OFP_DEFAULT_PRIORITY,
                                       inport, buf)
    #inst.send_openflow(dpid, bufid, buf, access_actions, inport)

  if len(trunk) > 0 :
    for p in trunk : 
      trunk_actions.append([openflow.OFPAT_OUTPUT, [0, p]])

    logger.debug("Flooding packet in trunk ports, actions %s" 
                   % str(trunk_actions))
    inst.install_datapath_flow(dpid, flow, IDLE_CACHE_TIMEOUT, 
                                       openflow.OFP_FLOW_PERMANENT,
                                       trunk_actions,
                                       bufid, openflow.OFP_DEFAULT_PRIORITY,
                                       inport, buf)
    #inst.send_openflow(dpid, None, buf, trunk_actions, inport)

# This function takes as parameter a dpid and the specific vlan tag
# and it returns a tuple of two lists of access and trunk ports 
# that are on that vlan. This function will return only ports that
# are currently active and are also part of the configuration
# Active ports are those that the switch has currently advertised to the
# controller, and ports of the configuration are those defined in vlan_conf
# The function returns the number of the ports as advertised by the switch 
# INPUT : 
#  * dpid : the dpid of the switch we care about
#  * vlan_id : the id of the vlan we want to get the ports on
# OUTPUT : 
#  * (access, trunk) : where
#       - access : a list of access ports on VLAN vlan_id
#       - trunk : a list of trunk ports carring VLAN vlan_id
# The fxn gets the configuration from vlan_conf
def get_active_ports_on_vlan(dpid, vlan_id) : 
  logger.debug("Get active ports for switch 0x%x on vlan %d"
                %(dpid, vlan_id))
  access = []
  trunk = []
  if not vlan_conf.has_key(dpid) :
    logger.info("Don't have vlan conf for DPID 0x%x, return empty lists" % dpid)
    return (access, trunk)
  for p in vlan_conf[dpid]['port_to_vlans'].keys() : 
    p_conf = vlan_conf[dpid]['port_to_vlans'][p]
    if vlan_id in p_conf[1] : 
      # check if the switch has advirtised 
      if port_name2no_map.has_key(dpid) and port_name2no_map[dpid].has_key(p) : 
        p_no = port_name2no_map[dpid][p]
        if p_conf[0] == 'a' :
          access.append(p_no)
        else : 
          trunk.append(p_no)

  logger.debug("access %s, trunk %s" %(str(access), str(trunk)))

  return (access, trunk)

        
def datapath_leave_callback(dpid):
    logger.info('Switch %x has left the network' % dpid)
    if inst.st.has_key(dpid):
        del inst.st[dpid]

def datapath_join_callback(dpid, stats):
    switch_stats[dpid] = stats
    logger.info('Switch %x has joined the network (%d switches currently connected)' 
                % (dpid, len(switch_stats)))
    logger.info('stats for switch : %s' % str(stats))
    # Keep the mapping between port number and port name
    if not port_name2no_map.has_key(dpid) :
        port_name2no_map[dpid] = {} 
        port_no2name_map[dpid] = {} 
    for p in stats['ports'] :
       port_name2no_map[dpid][p['name']] = int(p['port_no'])
       port_no2name_map[dpid][int(p['port_no'])] = p['name']

# --
# Packet entry method.
# Drop LLDP packets (or we get confused) and attempt learning and
# forwarding
# --
def packet_in_callback(dpid, inport, reason, len, bufid, packet):

    if not packet.parsed:
        log.msg('Ignoring incomplete packet',system='vlanswitch')
        
    if not inst.st.has_key(dpid):
        log.msg('registering new switch %x' % dpid,system='vlanswitch')
        inst.st[dpid] = {}

    # don't forward lldp packets    
    if packet.type == ethernet.VLAN_TYPE :
      vlan_hdr =packet.find('vlan')
      logger.debug('vlan hdr %s' % str(vlan_hdr))
      #if vlan_hdr.id == 1101 :
       # return CONTINUE
      pkt_type = vlan_hdr.eth_type
    else :
      logger.debug('packet is not tagged' )
      pkt_type = packet.type
    if pkt_type == ethernet.LLDP_TYPE:
        return CONTINUE
    
    logger.debug('RCVD PKT %s' % str(packet))

    inport_name = port_no2name_map[dpid][inport]
    logger.info('[%d:%s] %s -> %s ' % (dpid, inport_name, mac_to_str(packet.src), mac_to_str(packet.dst)))

    forward_packet(dpid, inport, packet, packet.arr, bufid)

    return CONTINUE

class vlanswitch(Component):

    def __init__(self, ctxt):
        global inst, vlan_conf
        Component.__init__(self, ctxt)
        self.st = {}
        if self.check_vlan_conf(vlan_conf) == False :
          exit
        inst = self

    def install(self):
        inst.register_for_packet_in(packet_in_callback)
        inst.register_for_datapath_leave(datapath_leave_callback)
        inst.register_for_datapath_join(datapath_join_callback)

    def getInterface(self):
        return str(vlanswitch)

    def check_vlan_conf(self, v_conf) :
      return True

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

    return Factory()
_______________________________________________
nox-dev mailing list
nox-dev@noxrepo.org
http://noxrepo.org/mailman/listinfo/nox-dev

Reply via email to