c0590ea903
This patch is generated by "2to3 -f dict" and irrevant parts were hand-removed. Signed-off-by: IWAMOTO Toshihiro <iwamoto@valinux.co.jp> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
1197 lines
46 KiB
Python
1197 lines
46 KiB
Python
# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
|
|
import datetime
|
|
import logging
|
|
|
|
from ryu.base import app_manager
|
|
from ryu.controller import event
|
|
from ryu.controller import handler
|
|
from ryu.controller import ofp_event
|
|
from ryu.controller.handler import set_ev_cls
|
|
from ryu.exception import RyuException
|
|
from ryu.exception import OFPUnknownVersion
|
|
from ryu.lib import hub
|
|
from ryu.lib.dpid import dpid_to_str
|
|
from ryu.lib.packet import bpdu
|
|
from ryu.lib.packet import ethernet
|
|
from ryu.lib.packet import llc
|
|
from ryu.lib.packet import packet
|
|
from ryu.ofproto import ofproto_v1_0
|
|
from ryu.ofproto import ofproto_v1_2
|
|
from ryu.ofproto import ofproto_v1_3
|
|
|
|
|
|
MAX_PORT_NO = 0xfff
|
|
|
|
# for OpenFlow 1.2/1.3
|
|
BPDU_PKT_IN_PRIORITY = 0xffff
|
|
NO_PKT_IN_PRIORITY = 0xfffe
|
|
|
|
|
|
# Result of compared config BPDU priority.
|
|
SUPERIOR = -1
|
|
REPEATED = 0
|
|
INFERIOR = 1
|
|
|
|
# Port role
|
|
DESIGNATED_PORT = 0 # The port which sends BPDU.
|
|
ROOT_PORT = 1 # The port which receives BPDU from a root bridge.
|
|
NON_DESIGNATED_PORT = 2 # The port which blocked.
|
|
|
|
""" How to decide the port roles.
|
|
|
|
Root bridge:
|
|
a bridge has smallest bridge ID is chosen as a root.
|
|
it sends original config BPDU.
|
|
Non Root bridge:
|
|
forwards config BPDU received from the root bridge.
|
|
|
|
+-----------------------+
|
|
| Root bridge |
|
|
+-----------------------+
|
|
(D) (D)
|
|
| |
|
|
| |
|
|
(R) (R)
|
|
+-----------------+ +-----------------+
|
|
| Non Root bridge |(D)---(ND)| Non Root bridge |
|
|
+-----------------+ +-----------------+
|
|
|
|
ROOT_PORT(R):
|
|
the nearest port to a root bridge of the bridge.
|
|
it is determined by the cost of the path, etc.
|
|
DESIGNATED_PORT(D):
|
|
the port of the side near the root bridge of each link.
|
|
it is determined by the cost of the path, etc.
|
|
NON_DESIGNATED_PORT(ND):
|
|
the port other than a ROOT_PORT and DESIGNATED_PORT.
|
|
"""
|
|
|
|
|
|
# Port state
|
|
# DISABLE: Administratively down or link down by an obstacle.
|
|
# BLOCK : Not part of spanning tree.
|
|
# LISTEN : Not learning or relaying frames.
|
|
# LEARN : Learning but not relaying frames.
|
|
# FORWARD: Learning and relaying frames.
|
|
PORT_STATE_DISABLE = 0
|
|
PORT_STATE_BLOCK = 1
|
|
PORT_STATE_LISTEN = 2
|
|
PORT_STATE_LEARN = 3
|
|
PORT_STATE_FORWARD = 4
|
|
|
|
# for OpenFlow 1.0
|
|
PORT_CONFIG_V1_0 = {PORT_STATE_DISABLE: (ofproto_v1_0.OFPPC_NO_RECV_STP
|
|
| ofproto_v1_0.OFPPC_NO_RECV
|
|
| ofproto_v1_0.OFPPC_NO_FLOOD
|
|
| ofproto_v1_0.OFPPC_NO_FWD),
|
|
PORT_STATE_BLOCK: (ofproto_v1_0.OFPPC_NO_RECV
|
|
| ofproto_v1_0.OFPPC_NO_FLOOD
|
|
| ofproto_v1_0.OFPPC_NO_FWD),
|
|
PORT_STATE_LISTEN: (ofproto_v1_0.OFPPC_NO_RECV
|
|
| ofproto_v1_0.OFPPC_NO_FLOOD),
|
|
PORT_STATE_LEARN: ofproto_v1_0.OFPPC_NO_FLOOD,
|
|
PORT_STATE_FORWARD: 0}
|
|
|
|
# for OpenFlow 1.2
|
|
PORT_CONFIG_V1_2 = {PORT_STATE_DISABLE: (ofproto_v1_2.OFPPC_NO_RECV
|
|
| ofproto_v1_2.OFPPC_NO_FWD),
|
|
PORT_STATE_BLOCK: (ofproto_v1_2.OFPPC_NO_FWD
|
|
| ofproto_v1_2.OFPPC_NO_PACKET_IN),
|
|
PORT_STATE_LISTEN: ofproto_v1_2.OFPPC_NO_PACKET_IN,
|
|
PORT_STATE_LEARN: ofproto_v1_2.OFPPC_NO_PACKET_IN,
|
|
PORT_STATE_FORWARD: 0}
|
|
|
|
# for OpenFlow 1.3
|
|
PORT_CONFIG_V1_3 = {PORT_STATE_DISABLE: (ofproto_v1_3.OFPPC_NO_RECV
|
|
| ofproto_v1_3.OFPPC_NO_FWD),
|
|
PORT_STATE_BLOCK: (ofproto_v1_3.OFPPC_NO_FWD
|
|
| ofproto_v1_3.OFPPC_NO_PACKET_IN),
|
|
PORT_STATE_LISTEN: ofproto_v1_3.OFPPC_NO_PACKET_IN,
|
|
PORT_STATE_LEARN: ofproto_v1_3.OFPPC_NO_PACKET_IN,
|
|
PORT_STATE_FORWARD: 0}
|
|
|
|
""" Port state machine
|
|
|
|
+------------------------<--------------------------+
|
|
| |*2
|
|
+--> [BLOCK] -----+--> [LISTEN] ----> [LEARN] ------+----> [FORWARD]
|
|
*3 | | 15sec | 15sec *1 |
|
|
| |*3 |*3 |*3
|
|
+----<---+------<------+----------<----------+
|
|
|
|
*1 if port role == DESIGNATED_PORT or ROOT_PORT
|
|
*2 if port role == NON_DESIGNATED_PORT
|
|
*3 re-calculation of Spanning tree occurred.
|
|
|
|
When bridge has started, each port state is set to [LISTEN]
|
|
except port configuration is disable.
|
|
If port configuration is disable or link down occurred,
|
|
the port state is set to [DISABLE]
|
|
"""
|
|
|
|
|
|
# Throw this event when network topology is changed.
|
|
# Flush filtering database, when you receive this event.
|
|
class EventTopologyChange(event.EventBase):
|
|
def __init__(self, dp):
|
|
super(EventTopologyChange, self).__init__()
|
|
self.dp = dp
|
|
|
|
|
|
# Throw this event when port status is changed.
|
|
class EventPortStateChange(event.EventBase):
|
|
def __init__(self, dp, port):
|
|
super(EventPortStateChange, self).__init__()
|
|
self.dp = dp
|
|
self.port_no = port.ofport.port_no
|
|
self.port_state = port.state
|
|
|
|
|
|
# Event for receive packet in message except BPDU packet.
|
|
class EventPacketIn(event.EventBase):
|
|
def __init__(self, msg):
|
|
super(EventPacketIn, self).__init__()
|
|
self.msg = msg
|
|
|
|
|
|
class Stp(app_manager.RyuApp):
|
|
""" STP(spanning tree) library. """
|
|
|
|
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
|
|
ofproto_v1_2.OFP_VERSION,
|
|
ofproto_v1_3.OFP_VERSION]
|
|
|
|
def __init__(self):
|
|
super(Stp, self).__init__()
|
|
self.name = 'stplib'
|
|
self._set_logger()
|
|
self.config = {}
|
|
self.bridge_list = {}
|
|
|
|
def close(self):
|
|
for dpid in self.bridge_list.keys():
|
|
self._unregister_bridge(dpid)
|
|
|
|
def _set_logger(self):
|
|
self.logger.propagate = False
|
|
hdlr = logging.StreamHandler()
|
|
fmt_str = '[STP][%(levelname)s] dpid=%(dpid)s: %(message)s'
|
|
hdlr.setFormatter(logging.Formatter(fmt_str))
|
|
self.logger.addHandler(hdlr)
|
|
|
|
def set_config(self, config):
|
|
""" Use this API if you want to set up configuration
|
|
of each bridge and ports.
|
|
Set configuration with 'config' parameter as follows.
|
|
|
|
config = {<dpid>: {'bridge': {'priority': <value>,
|
|
'sys_ext_id': <value>,
|
|
'max_age': <value>,
|
|
'hello_time': <value>,
|
|
'fwd_delay': <value>}
|
|
'ports': {<port_no>: {'priority': <value>,
|
|
'path_cost': <value>,
|
|
'enable': <True/False>},
|
|
<port_no>: {...},,,}}
|
|
<dpid>: {...},
|
|
<dpid>: {...},,,}
|
|
|
|
NOTE: You may omit each field.
|
|
If omitted, a default value is set up.
|
|
It becomes effective when a bridge starts.
|
|
|
|
Default values:
|
|
------------------------------------------------------
|
|
| bridge | priority | bpdu.DEFAULT_BRIDGE_PRIORITY |
|
|
| | sys_ext_id | 0 |
|
|
| | max_age | bpdu.DEFAULT_MAX_AGE |
|
|
| | hello_time | bpdu.DEFAULT_HELLO_TIME |
|
|
| | fwd_delay | bpdu.DEFAULT_FORWARD_DELAY |
|
|
|--------|------------|------------------------------|
|
|
| port | priority | bpdu.DEFAULT_PORT_PRIORITY |
|
|
| | path_cost | (Set up automatically |
|
|
| | | according to link speed.) |
|
|
| | enable | True |
|
|
------------------------------------------------------
|
|
"""
|
|
assert isinstance(config, dict)
|
|
self.config = config
|
|
|
|
@set_ev_cls(ofp_event.EventOFPStateChange,
|
|
[handler.MAIN_DISPATCHER, handler.DEAD_DISPATCHER])
|
|
def dispacher_change(self, ev):
|
|
assert ev.datapath is not None
|
|
if ev.state == handler.MAIN_DISPATCHER:
|
|
self._register_bridge(ev.datapath)
|
|
elif ev.state == handler.DEAD_DISPATCHER:
|
|
self._unregister_bridge(ev.datapath.id)
|
|
|
|
def _register_bridge(self, dp):
|
|
self._unregister_bridge(dp.id)
|
|
|
|
dpid_str = {'dpid': dpid_to_str(dp.id)}
|
|
self.logger.info('Join as stp bridge.', extra=dpid_str)
|
|
try:
|
|
bridge = Bridge(dp, self.logger,
|
|
self.config.get(dp.id, {}),
|
|
self.send_event_to_observers)
|
|
except OFPUnknownVersion as message:
|
|
self.logger.error(str(message), extra=dpid_str)
|
|
return
|
|
|
|
self.bridge_list[dp.id] = bridge
|
|
|
|
def _unregister_bridge(self, dp_id):
|
|
if dp_id in self.bridge_list:
|
|
self.bridge_list[dp_id].delete()
|
|
del self.bridge_list[dp_id]
|
|
self.logger.info('Leave stp bridge.',
|
|
extra={'dpid': dpid_to_str(dp_id)})
|
|
|
|
@set_ev_cls(ofp_event.EventOFPPacketIn, handler.MAIN_DISPATCHER)
|
|
def packet_in_handler(self, ev):
|
|
if ev.msg.datapath.id in self.bridge_list:
|
|
bridge = self.bridge_list[ev.msg.datapath.id]
|
|
bridge.packet_in_handler(ev.msg)
|
|
|
|
@set_ev_cls(ofp_event.EventOFPPortStatus, handler.MAIN_DISPATCHER)
|
|
def port_status_handler(self, ev):
|
|
dp = ev.msg.datapath
|
|
dpid_str = {'dpid': dpid_to_str(dp.id)}
|
|
port = ev.msg.desc
|
|
reason = ev.msg.reason
|
|
link_down_flg = port.state & 0b1
|
|
|
|
if dp.id in self.bridge_list:
|
|
bridge = self.bridge_list[dp.id]
|
|
|
|
if reason is dp.ofproto.OFPPR_ADD:
|
|
self.logger.info('[port=%d] Port add.',
|
|
port.port_no, extra=dpid_str)
|
|
bridge.port_add(port)
|
|
elif reason is dp.ofproto.OFPPR_DELETE:
|
|
self.logger.info('[port=%d] Port delete.',
|
|
port.port_no, extra=dpid_str)
|
|
bridge.port_delete(port.port_no)
|
|
else:
|
|
assert reason is dp.ofproto.OFPPR_MODIFY
|
|
if link_down_flg:
|
|
self.logger.info('[port=%d] Link down.',
|
|
port.port_no, extra=dpid_str)
|
|
bridge.link_down(port.port_no)
|
|
else:
|
|
self.logger.info('[port=%d] Link up.',
|
|
port.port_no, extra=dpid_str)
|
|
bridge.link_up(port.port_no)
|
|
|
|
@staticmethod
|
|
def compare_root_path(path_cost1, path_cost2, bridge_id1, bridge_id2,
|
|
port_id1, port_id2):
|
|
""" Decide the port of the side near a root bridge.
|
|
It is compared by the following priorities.
|
|
1. root path cost
|
|
2. designated bridge ID value
|
|
3. designated port ID value """
|
|
result = Stp._cmp_value(path_cost1, path_cost2)
|
|
if not result:
|
|
result = Stp._cmp_value(bridge_id1, bridge_id2)
|
|
if not result:
|
|
result = Stp._cmp_value(port_id1, port_id2)
|
|
return result
|
|
|
|
@staticmethod
|
|
def compare_bpdu_info(my_priority, my_times, rcv_priority, rcv_times):
|
|
""" Check received BPDU is superior to currently held BPDU
|
|
by the following comparison.
|
|
- root bridge ID value
|
|
- root path cost
|
|
- designated bridge ID value
|
|
- designated port ID value
|
|
- times """
|
|
if my_priority is None:
|
|
result = SUPERIOR
|
|
else:
|
|
result = Stp._cmp_value(rcv_priority.root_id.value,
|
|
my_priority.root_id.value)
|
|
if not result:
|
|
result = Stp.compare_root_path(
|
|
rcv_priority.root_path_cost,
|
|
my_priority.root_path_cost,
|
|
rcv_priority.designated_bridge_id.value,
|
|
my_priority.designated_bridge_id.value,
|
|
rcv_priority.designated_port_id.value,
|
|
my_priority.designated_port_id.value)
|
|
if not result:
|
|
result1 = Stp._cmp_value(
|
|
rcv_priority.designated_bridge_id.value,
|
|
my_priority.designated_bridge_id.mac_addr)
|
|
result2 = Stp._cmp_value(
|
|
rcv_priority.designated_port_id.value,
|
|
my_priority.designated_port_id.port_no)
|
|
if not result1 and not result2:
|
|
result = SUPERIOR
|
|
else:
|
|
result = Stp._cmp_obj(rcv_times, my_times)
|
|
return result
|
|
|
|
@staticmethod
|
|
def _cmp_value(value1, value2):
|
|
result = cmp(value1, value2)
|
|
if result < 0:
|
|
return SUPERIOR
|
|
elif result == 0:
|
|
return REPEATED
|
|
else:
|
|
return INFERIOR
|
|
|
|
@staticmethod
|
|
def _cmp_obj(obj1, obj2):
|
|
for key in obj1.__dict__.keys():
|
|
if (not hasattr(obj2, key)
|
|
or getattr(obj1, key) != getattr(obj2, key)):
|
|
return SUPERIOR
|
|
return REPEATED
|
|
|
|
|
|
class Bridge(object):
|
|
_DEFAULT_VALUE = {'priority': bpdu.DEFAULT_BRIDGE_PRIORITY,
|
|
'sys_ext_id': 0,
|
|
'max_age': bpdu.DEFAULT_MAX_AGE,
|
|
'hello_time': bpdu.DEFAULT_HELLO_TIME,
|
|
'fwd_delay': bpdu.DEFAULT_FORWARD_DELAY}
|
|
|
|
def __init__(self, dp, logger, config, send_ev_func):
|
|
super(Bridge, self).__init__()
|
|
self.dp = dp
|
|
self.logger = logger
|
|
self.dpid_str = {'dpid': dpid_to_str(dp.id)}
|
|
self.send_event = send_ev_func
|
|
|
|
# Bridge data
|
|
bridge_conf = config.get('bridge', {})
|
|
values = self._DEFAULT_VALUE
|
|
for key, value in bridge_conf.items():
|
|
values[key] = value
|
|
system_id = list(dp.ports.values())[0].hw_addr
|
|
|
|
self.bridge_id = BridgeId(values['priority'],
|
|
values['sys_ext_id'],
|
|
system_id)
|
|
self.bridge_times = Times(0, # message_age
|
|
values['max_age'],
|
|
values['hello_time'],
|
|
values['fwd_delay'])
|
|
# Root bridge data
|
|
self.root_priority = Priority(self.bridge_id, 0, None, None)
|
|
self.root_times = self.bridge_times
|
|
# Ports
|
|
self.ports = {}
|
|
self.ports_conf = config.get('ports', {})
|
|
for ofport in dp.ports.values():
|
|
self.port_add(ofport)
|
|
|
|
# Install BPDU PacketIn flow. (OpenFlow 1.2/1.3)
|
|
if dp.ofproto == ofproto_v1_2 or dp.ofproto == ofproto_v1_3:
|
|
ofctl = OfCtl_v1_2later(self.dp)
|
|
ofctl.add_bpdu_pkt_in_flow()
|
|
|
|
@property
|
|
def is_root_bridge(self):
|
|
return bool(self.bridge_id.value == self.root_priority.root_id.value)
|
|
|
|
def delete(self):
|
|
for port in self.ports.values():
|
|
port.delete()
|
|
|
|
def port_add(self, ofport):
|
|
if ofport.port_no <= MAX_PORT_NO:
|
|
port_conf = self.ports_conf.get(ofport.port_no, {})
|
|
self.ports[ofport.port_no] = Port(self.dp, self.logger,
|
|
port_conf, self.send_event,
|
|
self.recalculate_spanning_tree,
|
|
self.topology_change_notify,
|
|
self.bridge_id,
|
|
self.bridge_times,
|
|
ofport)
|
|
|
|
def port_delete(self, port_no):
|
|
self.link_down(port_no)
|
|
self.ports[port_no].delete()
|
|
del self.ports[port_no]
|
|
|
|
def link_up(self, port_no):
|
|
port = self.ports[port_no]
|
|
port.up(DESIGNATED_PORT, self.root_priority, self.root_times)
|
|
|
|
def link_down(self, port_no):
|
|
""" DESIGNATED_PORT/NON_DESIGNATED_PORT: change status to DISABLE.
|
|
ROOT_PORT: change status to DISABLE and recalculate STP. """
|
|
port = self.ports[port_no]
|
|
init_stp_flg = bool(port.role is ROOT_PORT)
|
|
|
|
port.down(PORT_STATE_DISABLE, msg_init=True)
|
|
if init_stp_flg:
|
|
self.recalculate_spanning_tree()
|
|
|
|
def packet_in_handler(self, msg):
|
|
dp = msg.datapath
|
|
if dp.ofproto == ofproto_v1_0:
|
|
in_port_no = msg.in_port
|
|
else:
|
|
assert dp.ofproto == ofproto_v1_2 or dp.ofproto == ofproto_v1_3
|
|
in_port_no = None
|
|
for match_field in msg.match.fields:
|
|
if match_field.header == dp.ofproto.OXM_OF_IN_PORT:
|
|
in_port_no = match_field.value
|
|
break
|
|
if in_port_no not in self.ports:
|
|
return
|
|
|
|
in_port = self.ports[in_port_no]
|
|
if in_port.state == PORT_STATE_DISABLE:
|
|
return
|
|
|
|
pkt = packet.Packet(msg.data)
|
|
if bpdu.ConfigurationBPDUs in pkt:
|
|
""" Receive Configuration BPDU.
|
|
- If receive superior BPDU:
|
|
re-caluculation of spanning tree.
|
|
- If receive Topology Change BPDU:
|
|
throw EventTopologyChange.
|
|
forward Topology Change BPDU. """
|
|
(bpdu_pkt, ) = pkt.get_protocols(bpdu.ConfigurationBPDUs)
|
|
if bpdu_pkt.message_age > bpdu_pkt.max_age:
|
|
log_msg = 'Drop BPDU packet which message_age exceeded.'
|
|
self.logger.debug(log_msg, extra=self.dpid_str)
|
|
return
|
|
|
|
rcv_info, rcv_tc = in_port.rcv_config_bpdu(bpdu_pkt)
|
|
|
|
if rcv_info is SUPERIOR:
|
|
self.logger.info('[port=%d] Receive superior BPDU.',
|
|
in_port_no, extra=self.dpid_str)
|
|
self.recalculate_spanning_tree(init=False)
|
|
|
|
elif rcv_tc:
|
|
self.send_event(EventTopologyChange(self.dp))
|
|
|
|
if in_port.role is ROOT_PORT:
|
|
self._forward_tc_bpdu(rcv_tc)
|
|
|
|
elif bpdu.TopologyChangeNotificationBPDUs in pkt:
|
|
""" Receive Topology Change Notification BPDU.
|
|
send Topology Change Ack BPDU.
|
|
throw EventTopologyChange.
|
|
- Root bridge:
|
|
send Topology Change BPDU from all port.
|
|
- Non root bridge:
|
|
send Topology Change Notification BPDU to root bridge. """
|
|
in_port.transmit_ack_bpdu()
|
|
self.topology_change_notify(None)
|
|
|
|
elif bpdu.RstBPDUs in pkt:
|
|
""" Receive Rst BPDU. """
|
|
# TODO: RSTP
|
|
pass
|
|
|
|
else:
|
|
""" Receive non BPDU packet.
|
|
throw EventPacketIn. """
|
|
self.send_event(EventPacketIn(msg))
|
|
|
|
def recalculate_spanning_tree(self, init=True):
|
|
""" Re-calculation of spanning tree. """
|
|
# All port down.
|
|
for port in self.ports.values():
|
|
if port.state is not PORT_STATE_DISABLE:
|
|
port.down(PORT_STATE_BLOCK, msg_init=init)
|
|
|
|
# Send topology change event.
|
|
if init:
|
|
self.send_event(EventTopologyChange(self.dp))
|
|
|
|
# Update tree roles.
|
|
port_roles = {}
|
|
self.root_priority = Priority(self.bridge_id, 0, None, None)
|
|
self.root_times = self.bridge_times
|
|
|
|
if init:
|
|
self.logger.info('Root bridge.', extra=self.dpid_str)
|
|
for port_no in self.ports.keys():
|
|
port_roles[port_no] = DESIGNATED_PORT
|
|
else:
|
|
(port_roles,
|
|
self.root_priority,
|
|
self.root_times) = self._spanning_tree_algorithm()
|
|
|
|
# All port up.
|
|
for port_no, role in port_roles.items():
|
|
if self.ports[port_no].state is not PORT_STATE_DISABLE:
|
|
self.ports[port_no].up(role, self.root_priority,
|
|
self.root_times)
|
|
|
|
def _spanning_tree_algorithm(self):
|
|
""" Update tree roles.
|
|
- Root bridge:
|
|
all port is DESIGNATED_PORT.
|
|
- Non root bridge:
|
|
select one ROOT_PORT and some DESIGNATED_PORT,
|
|
and the other port is set to NON_DESIGNATED_PORT."""
|
|
port_roles = {}
|
|
|
|
root_port = self._select_root_port()
|
|
|
|
if root_port is None:
|
|
# My bridge is a root bridge.
|
|
self.logger.info('Root bridge.', extra=self.dpid_str)
|
|
root_priority = self.root_priority
|
|
root_times = self.root_times
|
|
|
|
for port_no in self.ports.keys():
|
|
if self.ports[port_no].state is not PORT_STATE_DISABLE:
|
|
port_roles[port_no] = DESIGNATED_PORT
|
|
else:
|
|
# Other bridge is a root bridge.
|
|
self.logger.info('Non root bridge.', extra=self.dpid_str)
|
|
root_priority = root_port.designated_priority
|
|
root_times = root_port.designated_times
|
|
|
|
port_roles[root_port.ofport.port_no] = ROOT_PORT
|
|
|
|
d_ports = self._select_designated_port(root_port)
|
|
for port_no in d_ports:
|
|
port_roles[port_no] = DESIGNATED_PORT
|
|
|
|
for port in self.ports.values():
|
|
if port.state is not PORT_STATE_DISABLE:
|
|
port_roles.setdefault(port.ofport.port_no,
|
|
NON_DESIGNATED_PORT)
|
|
|
|
return port_roles, root_priority, root_times
|
|
|
|
def _select_root_port(self):
|
|
""" ROOT_PORT is the nearest port to a root bridge.
|
|
It is determined by the cost of path, etc. """
|
|
root_port = None
|
|
|
|
for port in self.ports.values():
|
|
root_msg = (self.root_priority if root_port is None
|
|
else root_port.designated_priority)
|
|
port_msg = port.designated_priority
|
|
if port.state is PORT_STATE_DISABLE or port_msg is None:
|
|
continue
|
|
if root_msg.root_id.value > port_msg.root_id.value:
|
|
result = SUPERIOR
|
|
elif root_msg.root_id.value == port_msg.root_id.value:
|
|
if root_msg.designated_bridge_id is None:
|
|
result = INFERIOR
|
|
else:
|
|
result = Stp.compare_root_path(
|
|
port_msg.root_path_cost,
|
|
root_msg.root_path_cost,
|
|
port_msg.designated_bridge_id.value,
|
|
root_msg.designated_bridge_id.value,
|
|
port_msg.designated_port_id.value,
|
|
root_msg.designated_port_id.value)
|
|
else:
|
|
result = INFERIOR
|
|
|
|
if result is SUPERIOR:
|
|
root_port = port
|
|
|
|
return root_port
|
|
|
|
def _select_designated_port(self, root_port):
|
|
""" DESIGNATED_PORT is a port of the side near the root bridge
|
|
of each link. It is determined by the cost of each path, etc
|
|
same as ROOT_PORT. """
|
|
d_ports = []
|
|
root_msg = root_port.designated_priority
|
|
|
|
for port in self.ports.values():
|
|
port_msg = port.designated_priority
|
|
if (port.state is PORT_STATE_DISABLE
|
|
or port.ofport.port_no == root_port.ofport.port_no):
|
|
continue
|
|
if (port_msg is None or
|
|
(port_msg.root_id.value != root_msg.root_id.value)):
|
|
d_ports.append(port.ofport.port_no)
|
|
else:
|
|
result = Stp.compare_root_path(
|
|
root_msg.root_path_cost,
|
|
port_msg.root_path_cost - port.path_cost,
|
|
self.bridge_id.value,
|
|
port_msg.designated_bridge_id.value,
|
|
port.port_id.value,
|
|
port_msg.designated_port_id.value)
|
|
if result is SUPERIOR:
|
|
d_ports.append(port.ofport.port_no)
|
|
|
|
return d_ports
|
|
|
|
def topology_change_notify(self, port_state):
|
|
notice = False
|
|
if port_state is PORT_STATE_FORWARD:
|
|
for port in self.ports.values():
|
|
if port.role is DESIGNATED_PORT:
|
|
notice = True
|
|
break
|
|
else:
|
|
notice = True
|
|
|
|
if notice:
|
|
self.send_event(EventTopologyChange(self.dp))
|
|
if self.is_root_bridge:
|
|
self._transmit_tc_bpdu()
|
|
else:
|
|
self._transmit_tcn_bpdu()
|
|
|
|
def _transmit_tc_bpdu(self):
|
|
for port in self.ports.values():
|
|
port.transmit_tc_bpdu()
|
|
|
|
def _transmit_tcn_bpdu(self):
|
|
root_port = None
|
|
for port in self.ports.values():
|
|
if port.role is ROOT_PORT:
|
|
root_port = port
|
|
break
|
|
if root_port:
|
|
root_port.transmit_tcn_bpdu()
|
|
|
|
def _forward_tc_bpdu(self, fwd_flg):
|
|
for port in self.ports.values():
|
|
port.send_tc_flg = fwd_flg
|
|
|
|
|
|
class Port(object):
|
|
_DEFAULT_VALUE = {'priority': bpdu.DEFAULT_PORT_PRIORITY,
|
|
'path_cost': bpdu.PORT_PATH_COST_10MB,
|
|
'enable': True}
|
|
|
|
def __init__(self, dp, logger, config, send_ev_func, timeout_func,
|
|
topology_change_func, bridge_id, bridge_times, ofport):
|
|
super(Port, self).__init__()
|
|
self.dp = dp
|
|
self.logger = logger
|
|
self.dpid_str = {'dpid': dpid_to_str(dp.id)}
|
|
self.config_enable = config.get('enable',
|
|
self._DEFAULT_VALUE['enable'])
|
|
self.send_event = send_ev_func
|
|
self.wait_bpdu_timeout = timeout_func
|
|
self.topology_change_notify = topology_change_func
|
|
self.ofctl = (OfCtl_v1_0(dp) if dp.ofproto == ofproto_v1_0
|
|
else OfCtl_v1_2later(dp))
|
|
|
|
# Bridge data
|
|
self.bridge_id = bridge_id
|
|
# Root bridge data
|
|
self.port_priority = None
|
|
self.port_times = None
|
|
# ofproto_v1_X_parser.OFPPhyPort data
|
|
self.ofport = ofport
|
|
# Port data
|
|
values = self._DEFAULT_VALUE
|
|
path_costs = {dp.ofproto.OFPPF_10MB_HD: bpdu.PORT_PATH_COST_10MB,
|
|
dp.ofproto.OFPPF_10MB_FD: bpdu.PORT_PATH_COST_10MB,
|
|
dp.ofproto.OFPPF_100MB_HD: bpdu.PORT_PATH_COST_100MB,
|
|
dp.ofproto.OFPPF_100MB_FD: bpdu.PORT_PATH_COST_100MB,
|
|
dp.ofproto.OFPPF_1GB_HD: bpdu.PORT_PATH_COST_1GB,
|
|
dp.ofproto.OFPPF_1GB_FD: bpdu.PORT_PATH_COST_1GB,
|
|
dp.ofproto.OFPPF_10GB_FD: bpdu.PORT_PATH_COST_10GB}
|
|
for rate in sorted(path_costs.keys(), reverse=True):
|
|
if ofport.curr & rate:
|
|
values['path_cost'] = path_costs[rate]
|
|
break
|
|
for key, value in values.items():
|
|
values[key] = value
|
|
self.port_id = PortId(values['priority'], ofport.port_no)
|
|
self.path_cost = values['path_cost']
|
|
self.state = (None if self.config_enable else PORT_STATE_DISABLE)
|
|
self.role = None
|
|
# Receive BPDU data
|
|
self.designated_priority = None
|
|
self.designated_times = None
|
|
# BPDU handling threads
|
|
self.send_bpdu_thread = PortThread(self._transmit_bpdu)
|
|
self.wait_bpdu_thread = PortThread(self._wait_bpdu_timer)
|
|
self.send_tc_flg = None
|
|
self.send_tc_timer = None
|
|
self.send_tcn_flg = None
|
|
self.wait_timer_event = None
|
|
# State machine thread
|
|
self.state_machine = PortThread(self._state_machine)
|
|
self.state_event = None
|
|
|
|
self.up(DESIGNATED_PORT,
|
|
Priority(bridge_id, 0, None, None),
|
|
bridge_times)
|
|
|
|
self.state_machine.start()
|
|
self.logger.debug('[port=%d] Start port state machine.',
|
|
self.ofport.port_no, extra=self.dpid_str)
|
|
|
|
def delete(self):
|
|
self.state_machine.stop()
|
|
self.send_bpdu_thread.stop()
|
|
self.wait_bpdu_thread.stop()
|
|
if self.state_event is not None:
|
|
self.state_event.set()
|
|
self.state_event = None
|
|
if self.wait_timer_event is not None:
|
|
self.wait_timer_event.set()
|
|
self.wait_timer_event = None
|
|
self.logger.debug('[port=%d] Stop port threads.',
|
|
self.ofport.port_no, extra=self.dpid_str)
|
|
|
|
def up(self, role, root_priority, root_times):
|
|
""" A port is started in the state of LISTEN. """
|
|
self.port_priority = root_priority
|
|
self.port_times = root_times
|
|
|
|
state = (PORT_STATE_LISTEN if self.config_enable
|
|
else PORT_STATE_DISABLE)
|
|
self._change_role(role)
|
|
self._change_status(state)
|
|
|
|
def down(self, state, msg_init=False):
|
|
""" A port will be in the state of DISABLE or BLOCK,
|
|
and be stopped. """
|
|
assert (state is PORT_STATE_DISABLE
|
|
or state is PORT_STATE_BLOCK)
|
|
if not self.config_enable:
|
|
return
|
|
|
|
if msg_init:
|
|
self.designated_priority = None
|
|
self.designated_times = None
|
|
|
|
self._change_role(DESIGNATED_PORT)
|
|
self._change_status(state)
|
|
|
|
def _state_machine(self):
|
|
""" Port state machine.
|
|
Change next status when timer is exceeded
|
|
or _change_status() method is called."""
|
|
role_str = {ROOT_PORT: 'ROOT_PORT ',
|
|
DESIGNATED_PORT: 'DESIGNATED_PORT ',
|
|
NON_DESIGNATED_PORT: 'NON_DESIGNATED_PORT'}
|
|
state_str = {PORT_STATE_DISABLE: 'DISABLE',
|
|
PORT_STATE_BLOCK: 'BLOCK',
|
|
PORT_STATE_LISTEN: 'LISTEN',
|
|
PORT_STATE_LEARN: 'LEARN',
|
|
PORT_STATE_FORWARD: 'FORWARD'}
|
|
|
|
if self.state is PORT_STATE_DISABLE:
|
|
self.ofctl.set_port_status(self.ofport, self.state)
|
|
|
|
while True:
|
|
self.logger.info('[port=%d] %s / %s', self.ofport.port_no,
|
|
role_str[self.role], state_str[self.state],
|
|
extra=self.dpid_str)
|
|
|
|
self.state_event = hub.Event()
|
|
timer = self._get_timer()
|
|
if timer:
|
|
timeout = hub.Timeout(timer)
|
|
try:
|
|
self.state_event.wait()
|
|
except hub.Timeout as t:
|
|
if t is not timeout:
|
|
err_msg = 'Internal error. Not my timeout.'
|
|
raise RyuException(msg=err_msg)
|
|
new_state = self._get_next_state()
|
|
self._change_status(new_state, thread_switch=False)
|
|
finally:
|
|
timeout.cancel()
|
|
else:
|
|
self.state_event.wait()
|
|
|
|
self.state_event = None
|
|
|
|
def _get_timer(self):
|
|
timer = {PORT_STATE_DISABLE: None,
|
|
PORT_STATE_BLOCK: None,
|
|
PORT_STATE_LISTEN: self.port_times.forward_delay,
|
|
PORT_STATE_LEARN: self.port_times.forward_delay,
|
|
PORT_STATE_FORWARD: None}
|
|
return timer[self.state]
|
|
|
|
def _get_next_state(self):
|
|
next_state = {PORT_STATE_DISABLE: None,
|
|
PORT_STATE_BLOCK: None,
|
|
PORT_STATE_LISTEN: PORT_STATE_LEARN,
|
|
PORT_STATE_LEARN: (PORT_STATE_FORWARD
|
|
if (self.role is ROOT_PORT or
|
|
self.role is DESIGNATED_PORT)
|
|
else PORT_STATE_BLOCK),
|
|
PORT_STATE_FORWARD: None}
|
|
return next_state[self.state]
|
|
|
|
def _change_status(self, new_state, thread_switch=True):
|
|
if new_state is not PORT_STATE_DISABLE:
|
|
self.ofctl.set_port_status(self.ofport, new_state)
|
|
|
|
if(new_state is PORT_STATE_FORWARD or
|
|
(self.state is PORT_STATE_FORWARD and
|
|
(new_state is PORT_STATE_DISABLE or
|
|
new_state is PORT_STATE_BLOCK))):
|
|
self.topology_change_notify(new_state)
|
|
|
|
if (new_state is PORT_STATE_DISABLE
|
|
or new_state is PORT_STATE_BLOCK):
|
|
self.send_tc_flg = False
|
|
self.send_tc_timer = None
|
|
self.send_tcn_flg = False
|
|
self.send_bpdu_thread.stop()
|
|
elif new_state is PORT_STATE_LISTEN:
|
|
self.send_bpdu_thread.start()
|
|
|
|
self.state = new_state
|
|
self.send_event(EventPortStateChange(self.dp, self))
|
|
|
|
if self.state_event is not None:
|
|
self.state_event.set()
|
|
self.state_event = None
|
|
if thread_switch:
|
|
hub.sleep(0) # For thread switching.
|
|
|
|
def _change_role(self, new_role):
|
|
if self.role is new_role:
|
|
return
|
|
self.role = new_role
|
|
if (new_role is ROOT_PORT
|
|
or new_role is NON_DESIGNATED_PORT):
|
|
self.wait_bpdu_thread.start()
|
|
else:
|
|
assert new_role is DESIGNATED_PORT
|
|
self.wait_bpdu_thread.stop()
|
|
|
|
def rcv_config_bpdu(self, bpdu_pkt):
|
|
# Check received BPDU is superior to currently held BPDU.
|
|
root_id = BridgeId(bpdu_pkt.root_priority,
|
|
bpdu_pkt.root_system_id_extension,
|
|
bpdu_pkt.root_mac_address)
|
|
root_path_cost = bpdu_pkt.root_path_cost
|
|
designated_bridge_id = BridgeId(bpdu_pkt.bridge_priority,
|
|
bpdu_pkt.bridge_system_id_extension,
|
|
bpdu_pkt.bridge_mac_address)
|
|
designated_port_id = PortId(bpdu_pkt.port_priority,
|
|
bpdu_pkt.port_number)
|
|
|
|
msg_priority = Priority(root_id, root_path_cost,
|
|
designated_bridge_id,
|
|
designated_port_id)
|
|
msg_times = Times(bpdu_pkt.message_age,
|
|
bpdu_pkt.max_age,
|
|
bpdu_pkt.hello_time,
|
|
bpdu_pkt.forward_delay)
|
|
|
|
rcv_info = Stp.compare_bpdu_info(self.designated_priority,
|
|
self.designated_times,
|
|
msg_priority, msg_times)
|
|
if rcv_info is SUPERIOR:
|
|
self.designated_priority = msg_priority
|
|
self.designated_times = msg_times
|
|
|
|
chk_flg = False
|
|
if ((rcv_info is SUPERIOR or rcv_info is REPEATED)
|
|
and (self.role is ROOT_PORT
|
|
or self.role is NON_DESIGNATED_PORT)):
|
|
self._update_wait_bpdu_timer()
|
|
chk_flg = True
|
|
elif(rcv_info is INFERIOR and self.role is DESIGNATED_PORT):
|
|
chk_flg = True
|
|
|
|
# Check TopologyChange flag.
|
|
rcv_tc = False
|
|
if chk_flg:
|
|
tc_flag_mask = 0b00000001
|
|
tcack_flag_mask = 0b10000000
|
|
if bpdu_pkt.flags & tc_flag_mask:
|
|
self.logger.debug('[port=%d] receive TopologyChange BPDU.',
|
|
self.ofport.port_no, extra=self.dpid_str)
|
|
rcv_tc = True
|
|
if bpdu_pkt.flags & tcack_flag_mask:
|
|
self.logger.debug('[port=%d] receive TopologyChangeAck BPDU.',
|
|
self.ofport.port_no, extra=self.dpid_str)
|
|
if self.send_tcn_flg:
|
|
self.send_tcn_flg = False
|
|
|
|
return rcv_info, rcv_tc
|
|
|
|
def _update_wait_bpdu_timer(self):
|
|
if self.wait_timer_event is not None:
|
|
self.wait_timer_event.set()
|
|
self.wait_timer_event = None
|
|
self.logger.debug('[port=%d] Wait BPDU timer is updated.',
|
|
self.ofport.port_no, extra=self.dpid_str)
|
|
hub.sleep(0) # For thread switching.
|
|
|
|
def _wait_bpdu_timer(self):
|
|
time_exceed = False
|
|
|
|
while True:
|
|
self.wait_timer_event = hub.Event()
|
|
message_age = (self.designated_times.message_age
|
|
if self.designated_times else 0)
|
|
timer = self.port_times.max_age - message_age
|
|
timeout = hub.Timeout(timer)
|
|
try:
|
|
self.wait_timer_event.wait()
|
|
except hub.Timeout as t:
|
|
if t is not timeout:
|
|
err_msg = 'Internal error. Not my timeout.'
|
|
raise RyuException(msg=err_msg)
|
|
self.logger.info('[port=%d] Wait BPDU timer is exceeded.',
|
|
self.ofport.port_no, extra=self.dpid_str)
|
|
time_exceed = True
|
|
finally:
|
|
timeout.cancel()
|
|
self.wait_timer_event = None
|
|
|
|
if time_exceed:
|
|
break
|
|
|
|
if time_exceed: # Bridge.recalculate_spanning_tree
|
|
hub.spawn(self.wait_bpdu_timeout)
|
|
|
|
def _transmit_bpdu(self):
|
|
while True:
|
|
# Send config BPDU packet if port role is DESIGNATED_PORT.
|
|
if self.role == DESIGNATED_PORT:
|
|
now = datetime.datetime.today()
|
|
if self.send_tc_timer and self.send_tc_timer < now:
|
|
self.send_tc_timer = None
|
|
self.send_tc_flg = False
|
|
|
|
if not self.send_tc_flg:
|
|
flags = 0b00000000
|
|
log_msg = '[port=%d] Send Config BPDU.'
|
|
else:
|
|
flags = 0b00000001
|
|
log_msg = '[port=%d] Send TopologyChange BPDU.'
|
|
bpdu_data = self._generate_config_bpdu(flags)
|
|
self.ofctl.send_packet_out(self.ofport.port_no, bpdu_data)
|
|
self.logger.debug(log_msg, self.ofport.port_no,
|
|
extra=self.dpid_str)
|
|
|
|
# Send Topology Change Notification BPDU until receive Ack.
|
|
if self.send_tcn_flg:
|
|
bpdu_data = self._generate_tcn_bpdu()
|
|
self.ofctl.send_packet_out(self.ofport.port_no, bpdu_data)
|
|
self.logger.debug('[port=%d] Send TopologyChangeNotify BPDU.',
|
|
self.ofport.port_no, extra=self.dpid_str)
|
|
|
|
hub.sleep(self.port_times.hello_time)
|
|
|
|
def transmit_tc_bpdu(self):
|
|
""" Set send_tc_flg to send Topology Change BPDU. """
|
|
if not self.send_tc_flg:
|
|
timer = datetime.timedelta(seconds=self.port_times.max_age
|
|
+ self.port_times.forward_delay)
|
|
self.send_tc_timer = datetime.datetime.today() + timer
|
|
self.send_tc_flg = True
|
|
|
|
def transmit_ack_bpdu(self):
|
|
""" Send Topology Change Ack BPDU. """
|
|
ack_flags = 0b10000001
|
|
bpdu_data = self._generate_config_bpdu(ack_flags)
|
|
self.ofctl.send_packet_out(self.ofport.port_no, bpdu_data)
|
|
|
|
def transmit_tcn_bpdu(self):
|
|
self.send_tcn_flg = True
|
|
|
|
def _generate_config_bpdu(self, flags):
|
|
src_mac = self.ofport.hw_addr
|
|
dst_mac = bpdu.BRIDGE_GROUP_ADDRESS
|
|
length = (bpdu.bpdu._PACK_LEN + bpdu.ConfigurationBPDUs.PACK_LEN
|
|
+ llc.llc._PACK_LEN + llc.ControlFormatU._PACK_LEN)
|
|
|
|
e = ethernet.ethernet(dst_mac, src_mac, length)
|
|
l = llc.llc(llc.SAP_BPDU, llc.SAP_BPDU, llc.ControlFormatU())
|
|
b = bpdu.ConfigurationBPDUs(
|
|
flags=flags,
|
|
root_priority=self.port_priority.root_id.priority,
|
|
root_mac_address=self.port_priority.root_id.mac_addr,
|
|
root_path_cost=self.port_priority.root_path_cost + self.path_cost,
|
|
bridge_priority=self.bridge_id.priority,
|
|
bridge_mac_address=self.bridge_id.mac_addr,
|
|
port_priority=self.port_id.priority,
|
|
port_number=self.ofport.port_no,
|
|
message_age=self.port_times.message_age + 1,
|
|
max_age=self.port_times.max_age,
|
|
hello_time=self.port_times.hello_time,
|
|
forward_delay=self.port_times.forward_delay)
|
|
|
|
pkt = packet.Packet()
|
|
pkt.add_protocol(e)
|
|
pkt.add_protocol(l)
|
|
pkt.add_protocol(b)
|
|
pkt.serialize()
|
|
|
|
return pkt.data
|
|
|
|
def _generate_tcn_bpdu(self):
|
|
src_mac = self.ofport.hw_addr
|
|
dst_mac = bpdu.BRIDGE_GROUP_ADDRESS
|
|
length = (bpdu.bpdu._PACK_LEN
|
|
+ bpdu.TopologyChangeNotificationBPDUs.PACK_LEN
|
|
+ llc.llc._PACK_LEN + llc.ControlFormatU._PACK_LEN)
|
|
|
|
e = ethernet.ethernet(dst_mac, src_mac, length)
|
|
l = llc.llc(llc.SAP_BPDU, llc.SAP_BPDU, llc.ControlFormatU())
|
|
b = bpdu.TopologyChangeNotificationBPDUs()
|
|
|
|
pkt = packet.Packet()
|
|
pkt.add_protocol(e)
|
|
pkt.add_protocol(l)
|
|
pkt.add_protocol(b)
|
|
pkt.serialize()
|
|
|
|
return pkt.data
|
|
|
|
|
|
class PortThread(object):
|
|
def __init__(self, function):
|
|
super(PortThread, self).__init__()
|
|
self.function = function
|
|
self.thread = None
|
|
|
|
def start(self):
|
|
self.stop()
|
|
self.thread = hub.spawn(self.function)
|
|
|
|
def stop(self):
|
|
if self.thread is not None:
|
|
hub.kill(self.thread)
|
|
hub.joinall([self.thread])
|
|
self.thread = None
|
|
|
|
|
|
class BridgeId(object):
|
|
def __init__(self, priority, system_id_extension, mac_addr):
|
|
super(BridgeId, self).__init__()
|
|
self.priority = priority
|
|
self.system_id_extension = system_id_extension
|
|
self.mac_addr = mac_addr
|
|
self.value = bpdu.ConfigurationBPDUs.encode_bridge_id(
|
|
priority, system_id_extension, mac_addr)
|
|
|
|
|
|
class PortId(object):
|
|
def __init__(self, priority, port_no):
|
|
super(PortId, self).__init__()
|
|
self.priority = priority
|
|
self.port_no = port_no
|
|
self.value = bpdu.ConfigurationBPDUs.encode_port_id(priority, port_no)
|
|
|
|
|
|
class Priority(object):
|
|
def __init__(self, root_id, root_path_cost,
|
|
designated_bridge_id, designated_port_id):
|
|
super(Priority, self).__init__()
|
|
self.root_id = root_id
|
|
self.root_path_cost = root_path_cost
|
|
self.designated_bridge_id = designated_bridge_id
|
|
self.designated_port_id = designated_port_id
|
|
|
|
|
|
class Times(object):
|
|
def __init__(self, message_age, max_age, hello_time, forward_delay):
|
|
super(Times, self).__init__()
|
|
self.message_age = message_age
|
|
self.max_age = max_age
|
|
self.hello_time = hello_time
|
|
self.forward_delay = forward_delay
|
|
|
|
|
|
class OfCtl_v1_0(object):
|
|
def __init__(self, dp):
|
|
super(OfCtl_v1_0, self).__init__()
|
|
self.dp = dp
|
|
|
|
def send_packet_out(self, out_port, data):
|
|
actions = [self.dp.ofproto_parser.OFPActionOutput(out_port, 0)]
|
|
self.dp.send_packet_out(buffer_id=self.dp.ofproto.OFP_NO_BUFFER,
|
|
in_port=self.dp.ofproto.OFPP_CONTROLLER,
|
|
actions=actions, data=data)
|
|
|
|
def set_port_status(self, port, state):
|
|
ofproto_parser = self.dp.ofproto_parser
|
|
mask = 0b1111111
|
|
msg = ofproto_parser.OFPPortMod(self.dp, port.port_no, port.hw_addr,
|
|
PORT_CONFIG_V1_0[state], mask,
|
|
port.advertised)
|
|
self.dp.send_msg(msg)
|
|
|
|
|
|
class OfCtl_v1_2later(OfCtl_v1_0):
|
|
def __init__(self, dp):
|
|
super(OfCtl_v1_2later, self).__init__(dp)
|
|
|
|
def set_port_status(self, port, state):
|
|
ofp = self.dp.ofproto
|
|
parser = self.dp.ofproto_parser
|
|
config = {ofproto_v1_2: PORT_CONFIG_V1_2,
|
|
ofproto_v1_3: PORT_CONFIG_V1_3}
|
|
|
|
# Only turn on the relevant bits defined on OpenFlow 1.2+, otherwise
|
|
# some switch that follows the specification strictly will report
|
|
# OFPPMFC_BAD_CONFIG error.
|
|
mask = 0b1100101
|
|
msg = parser.OFPPortMod(self.dp, port.port_no, port.hw_addr,
|
|
config[ofp][state], mask, port.advertised)
|
|
self.dp.send_msg(msg)
|
|
|
|
if config[ofp][state] & ofp.OFPPC_NO_PACKET_IN:
|
|
self.add_no_pkt_in_flow(port.port_no)
|
|
else:
|
|
self.del_no_pkt_in_flow(port.port_no)
|
|
|
|
def add_bpdu_pkt_in_flow(self):
|
|
ofp = self.dp.ofproto
|
|
parser = self.dp.ofproto_parser
|
|
|
|
match = parser.OFPMatch(eth_dst=bpdu.BRIDGE_GROUP_ADDRESS)
|
|
actions = [parser.OFPActionOutput(ofp.OFPP_CONTROLLER,
|
|
ofp.OFPCML_NO_BUFFER)]
|
|
inst = [parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS,
|
|
actions)]
|
|
mod = parser.OFPFlowMod(self.dp, priority=BPDU_PKT_IN_PRIORITY,
|
|
match=match, instructions=inst)
|
|
self.dp.send_msg(mod)
|
|
|
|
def add_no_pkt_in_flow(self, in_port):
|
|
parser = self.dp.ofproto_parser
|
|
|
|
match = parser.OFPMatch(in_port=in_port)
|
|
mod = parser.OFPFlowMod(self.dp, priority=NO_PKT_IN_PRIORITY,
|
|
match=match)
|
|
self.dp.send_msg(mod)
|
|
|
|
def del_no_pkt_in_flow(self, in_port):
|
|
ofp = self.dp.ofproto
|
|
parser = self.dp.ofproto_parser
|
|
|
|
match = parser.OFPMatch(in_port=in_port)
|
|
mod = parser.OFPFlowMod(self.dp, command=ofp.OFPFC_DELETE_STRICT,
|
|
out_port=ofp.OFPP_ANY, out_group=ofp.OFPG_ANY,
|
|
priority=NO_PKT_IN_PRIORITY, match=match)
|
|
self.dp.send_msg(mod)
|