os-ken/ryu/lib/bfdlib.py

969 lines
35 KiB
Python

# Copyright (C) 2014 Xinguard, Inc.
# 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.
"""
Implementation of Bidirectional Forwarding Detection for IPv4 (Single Hop)
This module provides a simple way to let Ryu act like a daemon for running
IPv4 single hop BFD (RFC5881).
Please note that:
* Demand mode and echo function are not yet supported.
* Mechanism on negotiating L2/L3 addresses for an established
session is not yet implemented.
* The interoperability of authentication support is not tested.
* Configuring a BFD session with too small interval may lead to
full of event queue and congestion of Openflow channels.
For deploying a low-latency configuration or with a large number
of BFD sessions, use standalone BFD daemon instead.
"""
import logging
import time
import random
import six
from ryu.base import app_manager
from ryu.controller import event
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.exception import RyuException
from ryu.ofproto.ether import ETH_TYPE_IP, ETH_TYPE_ARP
from ryu.ofproto import ofproto_v1_3
from ryu.ofproto import inet
from ryu.lib import hub
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.lib.packet import ipv4
from ryu.lib.packet import udp
from ryu.lib.packet import bfd
from ryu.lib.packet import arp
from ryu.lib.packet.arp import ARP_REQUEST, ARP_REPLY
LOG = logging.getLogger(__name__)
UINT16_MAX = (1 << 16) - 1
UINT32_MAX = (1 << 32) - 1
# RFC5881 Section 8
BFD_CONTROL_UDP_PORT = 3784
BFD_ECHO_UDP_PORT = 3785
class BFDSession(object):
"""BFD Session class.
An instance maintains a BFD session.
"""
def __init__(self, app, my_discr, dpid, ofport,
src_mac, src_ip, src_port,
dst_mac="FF:FF:FF:FF:FF:FF", dst_ip="255.255.255.255",
detect_mult=3,
desired_min_tx_interval=1000000,
required_min_rx_interval=1000000,
auth_type=0, auth_keys=None):
"""
Initialize a BFD session.
__init__ takes the corresponding args in this order.
.. tabularcolumns:: |l|L|
========================= ============================================
Argument Description
========================= ============================================
app The instance of BFDLib.
my_discr My Discriminator.
dpid Datapath ID of the BFD interface.
ofport Openflow port number of the BFD interface.
src_mac Source MAC address of the BFD interface.
src_ip Source IPv4 address of the BFD interface.
dst_mac (Optional) Destination MAC address of the
BFD interface.
dst_ip (Optional) Destination IPv4 address of the
BFD interface.
detect_mult (Optional) Detection time multiplier.
desired_min_tx_interval (Optional) Desired Min TX Interval.
(in microseconds)
required_min_rx_interval (Optional) Required Min RX Interval.
(in microseconds)
auth_type (Optional) Authentication type.
auth_keys (Optional) A dictionary of authentication
key chain which key is an integer of
*Auth Key ID* and value is a string of
*Password* or *Auth Key*.
========================= ============================================
Example::
sess = BFDSession(app=self.bfdlib,
my_discr=1,
dpid=1,
ofport=1,
src_mac="01:23:45:67:89:AB",
src_ip="192.168.1.1",
dst_mac="12:34:56:78:9A:BC",
dst_ip="192.168.1.2",
detect_mult=3,
desired_min_tx_interval=1000000,
required_min_rx_interval=1000000,
auth_type=bfd.BFD_AUTH_KEYED_SHA1,
auth_keys={1: "secret key 1",
2: "secret key 2"})
"""
auth_keys = auth_keys if auth_keys else {}
assert not (auth_type and len(auth_keys) == 0)
# RyuApp reference to BFDLib
self.app = app
# RFC5880 Section 6.8.1.
# BFD Internal Variables
self._session_state = bfd.BFD_STATE_DOWN
self._remote_session_state = bfd.BFD_STATE_DOWN
self._local_discr = my_discr
self._remote_discr = 0
self._local_diag = 0
self._desired_min_tx_interval = 1000000
self._required_min_rx_interval = required_min_rx_interval
self._remote_min_rx_interval = -1
# TODO: Demand mode is not yet supported.
self._demand_mode = 0
self._remote_demand_mode = 0
self._detect_mult = detect_mult
self._auth_type = auth_type
self._auth_keys = auth_keys
if self._auth_type in [bfd.BFD_AUTH_KEYED_MD5,
bfd.BFD_AUTH_METICULOUS_KEYED_MD5,
bfd.BFD_AUTH_KEYED_SHA1,
bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]:
self._rcv_auth_seq = 0
self._xmit_auth_seq = random.randint(0, UINT32_MAX)
self._auth_seq_known = 0
# BFD Runtime Variables
self._cfg_desired_min_tx_interval = desired_min_tx_interval
self._cfg_required_min_echo_rx_interval = 0
self._active_role = True
self._detect_time = 0
self._xmit_period = None
self._update_xmit_period()
self._is_polling = True
self._pending_final = False
# _enable_send indicates the switch of the periodic transmission of
# BFD Control packets.
self._enable_send = True
self._lock = None
# L2/L3/L4 Header fields
self.src_mac = src_mac
self.dst_mac = dst_mac
self.src_ip = src_ip
self.dst_ip = dst_ip
self.ipv4_id = random.randint(0, UINT16_MAX)
self.src_port = src_port
self.dst_port = BFD_CONTROL_UDP_PORT
if dst_mac == "FF:FF:FF:FF:FF:FF" or dst_ip == "255.255.255.255":
self._remote_addr_config = False
else:
self._remote_addr_config = True
# Switch and port associated to this BFD session.
self.dpid = dpid
self.datapath = None
self.ofport = ofport
# Spawn a periodic transmission loop for BFD Control packets.
hub.spawn(self._send_loop)
LOG.info("[BFD][%s][INIT] BFD Session initialized.",
hex(self._local_discr))
@property
def my_discr(self):
"""
Returns My Discriminator of the BFD session.
"""
return self._local_discr
@property
def your_discr(self):
"""
Returns Your Discriminator of the BFD session.
"""
return self._remote_discr
def set_remote_addr(self, dst_mac, dst_ip):
"""
Configure remote ethernet and IP addresses.
"""
self.dst_mac = dst_mac
self.dst_ip = dst_ip
if not (dst_mac == "FF:FF:FF:FF:FF:FF" or dst_ip == "255.255.255.255"):
self._remote_addr_config = True
LOG.info("[BFD][%s][REMOTE] Remote address configured: %s, %s.",
hex(self._local_discr), self.dst_ip, self.dst_mac)
def recv(self, bfd_pkt):
"""
BFD packet receiver.
"""
LOG.debug("[BFD][%s][RECV] BFD Control received: %s",
hex(self._local_discr), six.binary_type(bfd_pkt))
self._remote_discr = bfd_pkt.my_discr
self._remote_state = bfd_pkt.state
self._remote_demand_mode = bfd_pkt.flags & bfd.BFD_FLAG_DEMAND
if self._remote_min_rx_interval != bfd_pkt.required_min_rx_interval:
self._remote_min_rx_interval = bfd_pkt.required_min_rx_interval
# Update transmit interval (RFC5880 Section 6.8.2.)
self._update_xmit_period()
# TODO: Echo function (RFC5880 Page 35)
if bfd_pkt.flags & bfd.BFD_FLAG_FINAL and self._is_polling:
self._is_polling = False
# Check and update the session state (RFC5880 Page 35)
if self._session_state == bfd.BFD_STATE_ADMIN_DOWN:
return
if bfd_pkt.state == bfd.BFD_STATE_ADMIN_DOWN:
if self._session_state != bfd.BFD_STATE_DOWN:
self._set_state(bfd.BFD_STATE_DOWN,
bfd.BFD_DIAG_NEIG_SIG_SESS_DOWN)
else:
if self._session_state == bfd.BFD_STATE_DOWN:
if bfd_pkt.state == bfd.BFD_STATE_DOWN:
self._set_state(bfd.BFD_STATE_INIT)
elif bfd_pkt.state == bfd.BFD_STATE_INIT:
self._set_state(bfd.BFD_STATE_UP)
elif self._session_state == bfd.BFD_STATE_INIT:
if bfd_pkt.state in [bfd.BFD_STATE_INIT, bfd.BFD_STATE_UP]:
self._set_state(bfd.BFD_STATE_UP)
else:
if bfd_pkt.state == bfd.BFD_STATE_DOWN:
self._set_state(bfd.BFD_STATE_DOWN,
bfd.BFD_DIAG_NEIG_SIG_SESS_DOWN)
# TODO: Demand mode support.
if self._remote_demand_mode and \
self._session_state == bfd.BFD_STATE_UP and \
self._remote_session_state == bfd.BFD_STATE_UP:
self._enable_send = False
if not self._remote_demand_mode or \
self._session_state != bfd.BFD_STATE_UP or \
self._remote_session_state != bfd.BFD_STATE_UP:
if not self._enable_send:
self._enable_send = True
hub.spawn(self._send_loop)
# Update the detection time (RFC5880 Section 6.8.4.)
if self._detect_time == 0:
self._detect_time = bfd_pkt.desired_min_tx_interval * \
bfd_pkt.detect_mult / 1000000.0
# Start the timeout loop.
hub.spawn(self._recv_timeout_loop)
if bfd_pkt.flags & bfd.BFD_FLAG_POLL:
self._pending_final = True
self._detect_time = bfd_pkt.desired_min_tx_interval * \
bfd_pkt.detect_mult / 1000000.0
# Update the remote authentication sequence number.
if self._auth_type in [bfd.BFD_AUTH_KEYED_MD5,
bfd.BFD_AUTH_METICULOUS_KEYED_MD5,
bfd.BFD_AUTH_KEYED_SHA1,
bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]:
self._rcv_auth_seq = bfd_pkt.auth_cls.seq
self._auth_seq_known = 1
# Set the lock.
if self._lock is not None:
self._lock.set()
def _set_state(self, new_state, diag=None):
"""
Set the state of the BFD session.
"""
old_state = self._session_state
LOG.info("[BFD][%s][STATE] State changed from %s to %s.",
hex(self._local_discr),
bfd.BFD_STATE_NAME[old_state],
bfd.BFD_STATE_NAME[new_state])
self._session_state = new_state
if new_state == bfd.BFD_STATE_DOWN:
if diag is not None:
self._local_diag = diag
self._desired_min_tx_interval = 1000000
self._is_polling = True
self._update_xmit_period()
elif new_state == bfd.BFD_STATE_UP:
self._desired_min_tx_interval = self._cfg_desired_min_tx_interval
self._is_polling = True
self._update_xmit_period()
self.app.send_event_to_observers(
EventBFDSessionStateChanged(self, old_state, new_state))
def _recv_timeout_loop(self):
"""
A loop to check timeout of receiving remote BFD packet.
"""
while self._detect_time:
last_wait = time.time()
self._lock = hub.Event()
self._lock.wait(timeout=self._detect_time)
if self._lock.is_set():
# Authentication variable check (RFC5880 Section 6.8.1.)
if getattr(self, "_auth_seq_known", 0):
if last_wait > time.time() + 2 * self._detect_time:
self._auth_seq_known = 0
else:
# Check Detection Time expiration (RFC5880 section 6.8.4.)
LOG.info("[BFD][%s][RECV] BFD Session timed out.",
hex(self._local_discr))
if self._session_state not in [bfd.BFD_STATE_DOWN,
bfd.BFD_STATE_ADMIN_DOWN]:
self._set_state(bfd.BFD_STATE_DOWN,
bfd.BFD_DIAG_CTRL_DETECT_TIME_EXPIRED)
# Authentication variable check (RFC5880 Section 6.8.1.)
if getattr(self, "_auth_seq_known", 0):
self._auth_seq_known = 0
def _update_xmit_period(self):
"""
Update transmission period of the BFD session.
"""
# RFC5880 Section 6.8.7.
if self._desired_min_tx_interval > self._remote_min_rx_interval:
xmit_period = self._desired_min_tx_interval
else:
xmit_period = self._remote_min_rx_interval
# This updates the transmission period of BFD Control packets.
# (RFC5880 Section 6.8.2 & 6.8.3.)
if self._detect_mult == 1:
xmit_period *= random.randint(75, 90) / 100.0
else:
xmit_period *= random.randint(75, 100) / 100.0
self._xmit_period = xmit_period / 1000000.0
LOG.info("[BFD][%s][XMIT] Transmission period changed to %f",
hex(self._local_discr), self._xmit_period)
def _send_loop(self):
"""
A loop to proceed periodic BFD packet transmission.
"""
while self._enable_send:
hub.sleep(self._xmit_period)
# Send BFD packet. (RFC5880 Section 6.8.7.)
if self._remote_discr == 0 and not self._active_role:
continue
if self._remote_min_rx_interval == 0:
continue
if self._remote_demand_mode and \
self._session_state == bfd.BFD_STATE_UP and \
self._remote_session_state == bfd.BFD_STATE_UP and \
not self._is_polling:
continue
self._send()
def _send(self):
"""
BFD packet sender.
"""
# If the switch was not connected to controller, exit.
if self.datapath is None:
return
# BFD Flags Setup
flags = 0
if self._pending_final:
flags |= bfd.BFD_FLAG_FINAL
self._pending_final = False
self._is_polling = False
if self._is_polling:
flags |= bfd.BFD_FLAG_POLL
# Authentication Section
auth_cls = None
if self._auth_type:
auth_key_id = list(self._auth_keys.keys())[
random.randint(0, len(list(self._auth_keys.keys())) - 1)]
auth_key = self._auth_keys[auth_key_id]
if self._auth_type == bfd.BFD_AUTH_SIMPLE_PASS:
auth_cls = bfd.SimplePassword(auth_key_id=auth_key_id,
password=auth_key)
if self._auth_type in [bfd.BFD_AUTH_KEYED_MD5,
bfd.BFD_AUTH_METICULOUS_KEYED_MD5,
bfd.BFD_AUTH_KEYED_SHA1,
bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]:
if self._auth_type in [bfd.BFD_AUTH_KEYED_MD5,
bfd.BFD_AUTH_KEYED_SHA1]:
if random.randint(0, 1):
self._xmit_auth_seq = \
(self._xmit_auth_seq + 1) & UINT32_MAX
else:
self._xmit_auth_seq = \
(self._xmit_auth_seq + 1) & UINT32_MAX
auth_cls = bfd.bfd._auth_parsers[self._auth_type](
auth_key_id=auth_key_id,
seq=self._xmit_auth_seq,
auth_key=auth_key)
if auth_cls is not None:
flags |= bfd.BFD_FLAG_AUTH_PRESENT
if self._demand_mode and \
self._session_state == bfd.BFD_STATE_UP and \
self._remote_session_state == bfd.BFD_STATE_UP:
flags |= bfd.BFD_FLAG_DEMAND
diag = self._local_diag
state = self._session_state
detect_mult = self._detect_mult
my_discr = self._local_discr
your_discr = self._remote_discr
desired_min_tx_interval = self._desired_min_tx_interval
required_min_rx_interval = self._required_min_rx_interval
required_min_echo_rx_interval = self._cfg_required_min_echo_rx_interval
# Prepare for Ethernet/IP/UDP header fields
src_mac = self.src_mac
dst_mac = self.dst_mac
src_ip = self.src_ip
dst_ip = self.dst_ip
self.ipv4_id = (self.ipv4_id + 1) & UINT16_MAX
ipv4_id = self.ipv4_id
src_port = self.src_port
dst_port = self.dst_port
# Construct BFD Control packet
data = BFDPacket.bfd_packet(
src_mac=src_mac, dst_mac=dst_mac,
src_ip=src_ip, dst_ip=dst_ip, ipv4_id=ipv4_id,
src_port=src_port, dst_port=dst_port,
diag=diag, state=state, flags=flags, detect_mult=detect_mult,
my_discr=my_discr, your_discr=your_discr,
desired_min_tx_interval=desired_min_tx_interval,
required_min_rx_interval=required_min_rx_interval,
required_min_echo_rx_interval=required_min_echo_rx_interval,
auth_cls=auth_cls)
# Prepare for a datapath
datapath = self.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
actions = [parser.OFPActionOutput(self.ofport)]
out = parser.OFPPacketOut(datapath=datapath,
buffer_id=ofproto.OFP_NO_BUFFER,
in_port=ofproto.OFPP_CONTROLLER,
actions=actions,
data=data)
datapath.send_msg(out)
LOG.debug("[BFD][%s][SEND] BFD Control sent.", hex(self._local_discr))
class BFDPacket(object):
"""
BFDPacket class for parsing raw BFD packet, and generating BFD packet with
Ethernet, IPv4, and UDP headers.
"""
class BFDUnknownFormat(RyuException):
message = '%(msg)s'
@staticmethod
def bfd_packet(src_mac, dst_mac, src_ip, dst_ip, ipv4_id,
src_port, dst_port,
diag=0, state=0, flags=0, detect_mult=0,
my_discr=0, your_discr=0, desired_min_tx_interval=0,
required_min_rx_interval=0,
required_min_echo_rx_interval=0,
auth_cls=None):
"""
Generate BFD packet with Ethernet/IPv4/UDP encapsulated.
"""
# Generate ethernet header first.
pkt = packet.Packet()
eth_pkt = ethernet.ethernet(dst_mac, src_mac, ETH_TYPE_IP)
pkt.add_protocol(eth_pkt)
# IPv4 encapsulation
# set ToS to 192 (Network control/CS6)
# set TTL to 255 (RFC5881 Section 5.)
ipv4_pkt = ipv4.ipv4(proto=inet.IPPROTO_UDP, src=src_ip, dst=dst_ip,
tos=192, identification=ipv4_id, ttl=255)
pkt.add_protocol(ipv4_pkt)
# UDP encapsulation
udp_pkt = udp.udp(src_port=src_port, dst_port=dst_port)
pkt.add_protocol(udp_pkt)
# BFD payload
bfd_pkt = bfd.bfd(
ver=1, diag=diag, state=state, flags=flags,
detect_mult=detect_mult,
my_discr=my_discr, your_discr=your_discr,
desired_min_tx_interval=desired_min_tx_interval,
required_min_rx_interval=required_min_rx_interval,
required_min_echo_rx_interval=required_min_echo_rx_interval,
auth_cls=auth_cls)
pkt.add_protocol(bfd_pkt)
pkt.serialize()
return pkt.data
@staticmethod
def bfd_parse(data):
"""
Parse raw packet and return BFD class from packet library.
"""
pkt = packet.Packet(data)
i = iter(pkt)
eth_pkt = next(i)
assert isinstance(eth_pkt, ethernet.ethernet)
ipv4_pkt = next(i)
assert isinstance(ipv4_pkt, ipv4.ipv4)
udp_pkt = next(i)
assert isinstance(udp_pkt, udp.udp)
udp_payload = next(i)
return bfd.bfd.parser(udp_payload)[0]
class ARPPacket(object):
"""
ARPPacket class for parsing raw ARP packet, and generating ARP packet with
Ethernet header.
"""
class ARPUnknownFormat(RyuException):
message = '%(msg)s'
@staticmethod
def arp_packet(opcode, src_mac, src_ip, dst_mac, dst_ip):
"""
Generate ARP packet with ethernet encapsulated.
"""
# Generate ethernet header first.
pkt = packet.Packet()
eth_pkt = ethernet.ethernet(dst_mac, src_mac, ETH_TYPE_ARP)
pkt.add_protocol(eth_pkt)
# Use IPv4 ARP wrapper from packet library directly.
arp_pkt = arp.arp_ip(opcode, src_mac, src_ip, dst_mac, dst_ip)
pkt.add_protocol(arp_pkt)
pkt.serialize()
return pkt.data
@staticmethod
def arp_parse(data):
"""
Parse ARP packet, return ARP class from packet library.
"""
# Iteratize pkt
pkt = packet.Packet(data)
i = iter(pkt)
eth_pkt = next(i)
# Ensure it's an ethernet frame.
assert isinstance(eth_pkt, ethernet.ethernet)
arp_pkt = next(i)
if not isinstance(arp_pkt, arp.arp):
raise ARPPacket.ARPUnknownFormat()
if arp_pkt.opcode not in (ARP_REQUEST, ARP_REPLY):
raise ARPPacket.ARPUnknownFormat(
msg='unsupported opcode %d' % arp_pkt.opcode)
if arp_pkt.proto != ETH_TYPE_IP:
raise ARPPacket.ARPUnknownFormat(
msg='unsupported arp ethtype 0x%04x' % arp_pkt.proto)
return arp_pkt
class EventBFDSessionStateChanged(event.EventBase):
"""
An event class that notifies the state change of a BFD session.
"""
def __init__(self, session, old_state, new_state):
super(EventBFDSessionStateChanged, self).__init__()
self.session = session
self.old_state = old_state
self.new_state = new_state
class BFDLib(app_manager.RyuApp):
"""
BFD daemon library.
Add this library as a context in your app and use ``add_bfd_session``
function to establish a BFD session.
Example::
from ryu.base import app_manager
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib import bfdlib
from ryu.lib.packet import bfd
class Foo(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
_CONTEXTS = {
'bfdlib': bfdlib.BFDLib
}
def __init__(self, *args, **kwargs):
super(Foo, self).__init__(*args, **kwargs)
self.bfdlib = kwargs['bfdlib']
self.my_discr = \
self.bfdlib.add_bfd_session(dpid=1,
ofport=1,
src_mac="00:23:45:67:89:AB",
src_ip="192.168.1.1")
@set_ev_cls(bfdlib.EventBFDSessionStateChanged)
def bfd_state_handler(self, ev):
if ev.session.my_discr != self.my_discr:
return
if ev.new_state == bfd.BFD_STATE_DOWN:
print "BFD Session=%d is DOWN!" % ev.session.my_discr
elif ev.new_state == bfd.BFD_STATE_UP:
print "BFD Session=%d is UP!" % ev.session.my_discr
"""
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
_EVENTS = [EventBFDSessionStateChanged]
def __init__(self, *args, **kwargs):
super(BFDLib, self).__init__(*args, **kwargs)
# BFD Session Dictionary
# key: My Discriminator
# value: BFDSession object
self.session = {}
def close(self):
pass
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# Update datapath object in BFD sessions
for s in self.session.values():
if s.dpid == datapath.id:
s.datapath = datapath
# Install default flows for capturing ARP & BFD packets.
match = parser.OFPMatch(eth_type=ETH_TYPE_ARP)
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0xFFFF, match, actions)
match = parser.OFPMatch(eth_type=ETH_TYPE_IP,
ip_proto=inet.IPPROTO_UDP,
udp_dst=3784)
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0xFFFF, match, actions)
def add_flow(self, datapath, priority, match, actions):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst)
datapath.send_msg(mod)
# Packet-In Handler, only for BFD packets.
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
in_port = msg.match['in_port']
pkt = packet.Packet(msg.data)
# If there's someone asked for an IP address associated
# with a BFD session, generate an ARP reply for it.
if arp.arp in pkt:
arp_pkt = ARPPacket.arp_parse(msg.data)
if arp_pkt.opcode == ARP_REQUEST:
for s in self.session.values():
if s.dpid == datapath.id and \
s.ofport == in_port and \
s.src_ip == arp_pkt.dst_ip:
ans = ARPPacket.arp_packet(
ARP_REPLY,
s.src_mac, s.src_ip,
arp_pkt.src_mac, arp_pkt.src_ip)
actions = [parser.OFPActionOutput(in_port)]
out = parser.OFPPacketOut(
datapath=datapath,
buffer_id=ofproto.OFP_NO_BUFFER,
in_port=ofproto.OFPP_CONTROLLER,
actions=actions, data=ans)
datapath.send_msg(out)
return
return
# Check whether it's BFD packet or not.
if ipv4.ipv4 not in pkt or udp.udp not in pkt:
return
udp_hdr = pkt.get_protocols(udp.udp)[0]
if udp_hdr.dst_port != BFD_CONTROL_UDP_PORT:
return
# Parse BFD packet here.
self.recv_bfd_pkt(datapath, in_port, msg.data)
def add_bfd_session(self, dpid, ofport, src_mac, src_ip,
dst_mac="FF:FF:FF:FF:FF:FF", dst_ip="255.255.255.255",
auth_type=0, auth_keys=None):
"""
Establish a new BFD session and return My Discriminator of new session.
Configure the BFD session with the following arguments.
================ ======================================================
Argument Description
================ ======================================================
dpid Datapath ID of the BFD interface.
ofport Openflow port number of the BFD interface.
src_mac Source MAC address of the BFD interface.
src_ip Source IPv4 address of the BFD interface.
dst_mac (Optional) Destination MAC address of the BFD
interface.
dst_ip (Optional) Destination IPv4 address of the BFD
interface.
auth_type (Optional) Authentication type.
auth_keys (Optional) A dictionary of authentication key chain
which key is an integer of *Auth Key ID* and value
is a string of *Password* or *Auth Key*.
================ ======================================================
Example::
add_bfd_session(dpid=1,
ofport=1,
src_mac="01:23:45:67:89:AB",
src_ip="192.168.1.1",
dst_mac="12:34:56:78:9A:BC",
dst_ip="192.168.1.2",
auth_type=bfd.BFD_AUTH_KEYED_SHA1,
auth_keys={1: "secret key 1",
2: "secret key 2"})
"""
auth_keys = auth_keys if auth_keys else {}
# Generate a unique discriminator
while True:
# Generate My Discriminator
my_discr = random.randint(1, UINT32_MAX)
# Generate an UDP destination port according to RFC5881 Section 4.
src_port = random.randint(49152, 65535)
# Ensure generated discriminator and UDP port are unique.
if my_discr in self.session:
continue
unique_flag = True
for s in self.session.values():
if s.your_discr == my_discr or s.src_port == src_port:
unique_flag = False
break
if unique_flag:
break
sess = BFDSession(app=self, my_discr=my_discr,
dpid=dpid, ofport=ofport,
src_mac=src_mac, src_ip=src_ip, src_port=src_port,
dst_mac=dst_mac, dst_ip=dst_ip,
auth_type=auth_type, auth_keys=auth_keys)
self.session[my_discr] = sess
return my_discr
def recv_bfd_pkt(self, datapath, in_port, data):
pkt = packet.Packet(data)
eth = pkt.get_protocols(ethernet.ethernet)[0]
if eth.ethertype != ETH_TYPE_IP:
return
ip_pkt = pkt.get_protocols(ipv4.ipv4)[0]
# Discard it if TTL != 255 for single hop bfd. (RFC5881 Section 5.)
if ip_pkt.ttl != 255:
return
# Parse BFD packet here.
bfd_pkt = BFDPacket.bfd_parse(data)
if not isinstance(bfd_pkt, bfd.bfd):
return
# BFD sanity checks
# RFC 5880 Section 6.8.6.
if bfd_pkt.ver != 1:
return
if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT:
if bfd_pkt.length < 26:
return
else:
if bfd_pkt.length < 24:
return
if bfd_pkt.detect_mult == 0:
return
if bfd_pkt.flags & bfd.BFD_FLAG_MULTIPOINT:
return
if bfd_pkt.my_discr == 0:
return
if bfd_pkt.your_discr != 0 and bfd_pkt.your_discr not in self.session:
return
if bfd_pkt.your_discr == 0 and \
bfd_pkt.state not in [bfd.BFD_STATE_ADMIN_DOWN,
bfd.BFD_STATE_DOWN]:
return
sess_my_discr = None
if bfd_pkt.your_discr == 0:
# Select session (Page 34)
for s in self.session.values():
if s.dpid == datapath.id and s.ofport == in_port:
sess_my_discr = s.my_discr
break
# BFD Session not found.
if sess_my_discr is None:
return
else:
sess_my_discr = bfd_pkt.your_discr
sess = self.session[sess_my_discr]
if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT and sess._auth_type == 0:
return
if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT == 0 and \
sess._auth_type != 0:
return
# Authenticate the session (Section 6.7.)
if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT:
if sess._auth_type == 0:
return
if bfd_pkt.auth_cls.auth_type != sess._auth_type:
return
# Check authentication sequence number to defend replay attack.
if sess._auth_type in [bfd.BFD_AUTH_KEYED_MD5,
bfd.BFD_AUTH_METICULOUS_KEYED_MD5,
bfd.BFD_AUTH_KEYED_SHA1,
bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]:
if sess._auth_seq_known:
if bfd_pkt.auth_cls.seq < sess._rcv_auth_seq:
return
if sess._auth_type in [bfd.BFD_AUTH_METICULOUS_KEYED_MD5,
bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]:
if bfd_pkt.auth_cls.seq <= sess._rcv_auth_seq:
return
if bfd_pkt.auth_cls.seq > sess._rcv_auth_seq \
+ 3 * sess._detect_mult:
return
if not bfd_pkt.authenticate(sess._auth_keys):
LOG.debug("[BFD][%s][AUTH] BFD Control authentication failed.",
hex(sess._local_discr))
return
# Sanity check passed, proceed.
if sess is not None:
# Check whether L2/L3 addresses were configured or not.
# TODO: L2/L3 addresses negotiation for an established session.
if not sess._remote_addr_config:
sess.set_remote_addr(eth.src, ip_pkt.src)
# Proceed to session update.
sess.recv(bfd_pkt)