os-ken/os_ken/lib/bfdlib.py

971 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 OSKen 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 os_ken.base import app_manager
from os_ken.controller import event
from os_ken.controller import ofp_event
from os_ken.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from os_ken.controller.handler import set_ev_cls
from os_ken.exception import OSKenException
from os_ken.ofproto.ether import ETH_TYPE_IP, ETH_TYPE_ARP
from os_ken.ofproto import ofproto_v1_3
from os_ken.ofproto import inet
from os_ken.lib import hub
from os_ken.lib.packet import packet
from os_ken.lib.packet import ethernet
from os_ken.lib.packet import ipv4
from os_ken.lib.packet import udp
from os_ken.lib.packet import bfd
from os_ken.lib.packet import arp
from os_ken.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)
# OSKenApp 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(OSKenException):
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(OSKenException):
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.OSKenApp):
"""
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 os_ken.base import app_manager
from os_ken.controller.handler import set_ev_cls
from os_ken.ofproto import ofproto_v1_3
from os_ken.lib import bfdlib
from os_ken.lib.packet import bfd
class Foo(app_manager.OSKenApp):
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)