"""Utility functions to parse/create OpenFlow messages."""
# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
# Copyright (C) 2015 Brad Cowie, Christopher Lorier and Joe Stringer.
# Copyright (C) 2015 Research and Education Advanced Network New Zealand Ltd.
# Copyright (C) 2015--2017 The Contributors
#
# 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 ipaddress
from ryu.lib import mac
from ryu.lib import ofctl_v1_3 as ofctl
from ryu.lib.ofctl_utils import str_to_int, to_match_ip, to_match_masked_int, to_match_eth, to_match_vid, OFCtlUtil
from ryu.ofproto import ether
from ryu.ofproto import inet
from ryu.ofproto import ofproto_v1_3 as ofp
from ryu.ofproto import ofproto_v1_3_parser as parser
from faucet.valve_of_old import OLD_MATCH_FIELDS
MIN_VID = 1
MAX_VID = 4095
VLAN_GROUP_OFFSET = MAX_VID + 1
ROUTE_GROUP_OFFSET = VLAN_GROUP_OFFSET * 2
OFP_VERSIONS = [ofp.OFP_VERSION]
OFP_IN_PORT = ofp.OFPP_IN_PORT
MAX_PACKET_IN_BYTES = 128
[docs]def ignore_port(port_num):
"""Return True if FAUCET should ignore this port.
Args:
port_num (int): switch port.
Returns:
bool: True if FAUCET should ignore this port.
"""
# special case OFPP_LOCAL to allow FAUCET to manage switch admin interface.
if port_num == ofp.OFPP_LOCAL:
return False
# 0xF0000000 and up are not physical ports.
return port_num > 0xF0000000
[docs]def is_flowmod(ofmsg):
"""Return True if flow message is a FlowMod.
Args:
ofmsg: ryu.ofproto.ofproto_v1_3_parser message.
Returns:
bool: True if is a FlowMod
"""
return isinstance(ofmsg, parser.OFPFlowMod)
[docs]def is_groupmod(ofmsg):
"""Return True if OF message is a GroupMod.
Args:
ofmsg: ryu.ofproto.ofproto_v1_3_parser message.
Returns:
bool: True if is a GroupMod
"""
return isinstance(ofmsg, parser.OFPGroupMod)
[docs]def is_metermod(ofmsg):
"""Return True if OF message is a MeterMod.
Args:
ofmsg: ryu.ofproto.ofproto_v1_3_parser message.
Returns:
bool: True if is a MeterMod
"""
return isinstance(ofmsg, parser.OFPMeterMod)
[docs]def is_flowdel(ofmsg):
"""Return True if flow message is a FlowMod and a delete.
Args:
ofmsg: ryu.ofproto.ofproto_v1_3_parser message.
Returns:
bool: True if is a FlowMod delete/strict.
"""
if (is_flowmod(ofmsg) and
(ofmsg.command == ofp.OFPFC_DELETE or
ofmsg.command == ofp.OFPFC_DELETE_STRICT)):
return True
return False
[docs]def is_groupdel(ofmsg):
"""Return True if OF message is a GroupMod and command is delete.
Args:
ofmsg: ryu.ofproto.ofproto_v1_3_parser message.
Returns:
bool: True if is a GroupMod delete
"""
if (is_groupmod(ofmsg) and
(ofmsg.command == ofp.OFPGC_DELETE)):
return True
return False
[docs]def is_meterdel(ofmsg):
"""Return True if OF message is a MeterMod and command is delete.
Args:
ofmsg: ryu.ofproto.ofproto_v1_3_parser message.
Returns:
bool: True if is a MeterMod delete
"""
if (is_metermod(ofmsg) and
(ofmsg.command == ofp.OFPMC_DELETE)):
return True
return False
[docs]def is_groupadd(ofmsg):
"""Return True if OF message is a GroupMod and command is add.
Args:
ofmsg: ryu.ofproto.ofproto_v1_3_parser message.
Returns:
bool: True if is a GroupMod add
"""
if (is_groupmod(ofmsg) and
(ofmsg.command == ofp.OFPGC_ADD)):
return True
return False
[docs]def is_meteradd(ofmsg):
"""Return True if OF message is a MeterMod and command is add.
Args:
ofmsg: ryu.ofproto.ofproto_v1_3_parser message.
Returns:
bool: True if is a MeterMod add
"""
if (is_metermod(ofmsg) and
(ofmsg.command == ofp.OFPMC_ADD)):
return True
return False
[docs]def apply_meter(meter_id):
"""Return instruction to apply a meter."""
return parser.OFPInstructionMeter(meter_id, ofp.OFPIT_METER)
[docs]def apply_actions(actions):
"""Return instruction that applies action list.
Args:
actions (list): list of OpenFlow actions.
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPInstruction: instruction of actions.
"""
return parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)
[docs]def goto_table(table):
"""Return instruction to goto table.
Args:
table (ValveTable): table to goto.
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPInstruction: goto instruction.
"""
return parser.OFPInstructionGotoTable(table.table_id)
[docs]def set_eth_src(eth_src):
"""Return action to set source Ethernet MAC address.
Args:
eth_src (str): source Ethernet MAC address.
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPActionSetField: set field action.
"""
return parser.OFPActionSetField(eth_src=eth_src)
[docs]def set_eth_dst(eth_dst):
"""Return action to set destination Ethernet MAC address.
Args:
eth_src (str): destination Ethernet MAC address.
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPActionSetField: set field action.
"""
return parser.OFPActionSetField(eth_dst=eth_dst)
[docs]def vid_present(vid):
"""Return VLAN VID with VID_PRESENT flag set.
Args:
vid (int): VLAN VID
Returns:
int: VLAN VID with VID_PRESENT.
"""
return vid | ofp.OFPVID_PRESENT
[docs]def devid_present(vid):
"""Return VLAN VID without VID_PRESENT flag set.
Args:
vid (int): VLAN VID with VID_PRESENT.
Returns:
int: VLAN VID.
"""
return vid ^ ofp.OFPVID_PRESENT
[docs]def set_vlan_vid(vlan_vid):
"""Set VLAN VID with VID_PRESENT flag set.
Args:
vid (int): VLAN VID
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPActionSetField: set VID with VID_PRESENT.
"""
return parser.OFPActionSetField(vlan_vid=vid_present(vlan_vid))
[docs]def push_vlan_act(vlan_vid, eth_type=ether.ETH_TYPE_8021Q):
"""Return OpenFlow action list to push Ethernet 802.1Q header with VLAN VID.
Args:
vid (int): VLAN VID
Returns:
list: actions to push 802.1Q header with VLAN VID set.
"""
return [
parser.OFPActionPushVlan(eth_type),
set_vlan_vid(vlan_vid),
]
[docs]def dec_ip_ttl():
"""Return OpenFlow action to decrement IP TTL.
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPActionDecNwTtl: decrement IP TTL.
"""
return parser.OFPActionDecNwTtl()
[docs]def pop_vlan():
"""Return OpenFlow action to pop outermost Ethernet 802.1Q VLAN header.
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPActionPopVlan: Pop VLAN.
"""
return parser.OFPActionPopVlan()
[docs]def output_port(port_num, max_len=0):
"""Return OpenFlow action to output to a port.
Args:
port_num (int): port to output to.
max_len (int): maximum length of packet to output (default no maximum).
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPActionOutput: output to port action.
"""
return parser.OFPActionOutput(port_num, max_len=max_len)
[docs]def output_in_port():
"""Return OpenFlow action to output out input port.
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPActionOutput.
"""
return output_port(OFP_IN_PORT)
[docs]def output_controller(max_len=MAX_PACKET_IN_BYTES):
"""Return OpenFlow action to packet in to the controller.
Args:
max_len (int): max number of bytes from packet to output.
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPActionOutput: packet in action.
"""
return output_port(ofp.OFPP_CONTROLLER, max_len)
[docs]def packetout(port_num, data):
"""Return OpenFlow action to packet out to dataplane from controller.
Args:
port_num (int): port to output to.
data (str): raw packet to output.
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPActionOutput: packet out action.
"""
return parser.OFPPacketOut(
datapath=None,
buffer_id=ofp.OFP_NO_BUFFER,
in_port=ofp.OFPP_CONTROLLER,
actions=[output_port(port_num)],
data=data)
[docs]def barrier():
"""Return OpenFlow barrier request.
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPBarrierRequest: barrier request.
"""
return parser.OFPBarrierRequest(None)
[docs]def table_features(body):
return parser.OFPTableFeaturesStatsRequest(
datapath=None, body=body)
[docs]def match(match_fields):
"""Return OpenFlow matches from dict.
Args:
match_fields (dict): match fields and values.
Returns:
ryu.ofproto.ofproto_v1_3_parser.OFPMatch: matches.
"""
return parser.OFPMatch(**match_fields)
[docs]def valve_match_vid(value):
return to_match_vid(value, ofp.OFPVID_PRESENT)
# See 7.2.3.7 Flow Match Fields (OF 1.3.5)
MATCH_FIELDS = {
'in_port': OFCtlUtil(ofp).ofp_port_from_user,
'in_phy_port': str_to_int,
'metadata': to_match_masked_int,
'eth_dst': to_match_eth,
'eth_src': to_match_eth,
'eth_type': str_to_int,
'vlan_vid': valve_match_vid,
'vlan_pcp': str_to_int,
'ip_dscp': str_to_int,
'ip_ecn': str_to_int,
'ip_proto': str_to_int,
'ipv4_src': to_match_ip,
'ipv4_dst': to_match_ip,
'tcp_src': to_match_masked_int,
'tcp_dst': to_match_masked_int,
'udp_src': to_match_masked_int,
'udp_dst': to_match_masked_int,
'sctp_src': to_match_masked_int,
'sctp_dst': to_match_masked_int,
'icmpv4_type': str_to_int,
'icmpv4_code': str_to_int,
'arp_op': str_to_int,
'arp_spa': to_match_ip,
'arp_tpa': to_match_ip,
'arp_sha': to_match_eth,
'arp_tha': to_match_eth,
'ipv6_src': to_match_ip,
'ipv6_dst': to_match_ip,
'ipv6_flabel': str_to_int,
'icmpv6_type': str_to_int,
'icmpv6_code': str_to_int,
'ipv6_nd_target': to_match_ip,
'ipv6_nd_sll': to_match_eth,
'ipv6_nd_tll': to_match_eth,
'mpls_label': str_to_int,
'mpls_tc': str_to_int,
'mpls_bos': str_to_int,
'pbb_isid': to_match_masked_int,
'tunnel_id': to_match_masked_int,
'ipv6_exthdr': to_match_masked_int
}
[docs]def match_from_dict(match_dict):
for old_match, new_match in list(OLD_MATCH_FIELDS.items()):
if old_match in match_dict:
match_dict[new_match] = match_dict[old_match]
del match_dict[old_match]
kwargs = {}
for match, field in list(match_dict.items()):
assert match in MATCH_FIELDS, 'Unknown match field: %s' % match
try:
encoded_field = MATCH_FIELDS[match](field)
except TypeError:
assert False, '%s cannot be type %s' % (match, type(field))
kwargs[match] = encoded_field
return parser.OFPMatch(**kwargs)
def _match_ip_masked(ipa):
if isinstance(ipa, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
return (str(ipa.network_address), str(ipa.netmask))
return (str(ipa.ip), str(ipa.netmask))
[docs]def build_match_dict(in_port=None, vlan=None,
eth_type=None, eth_src=None,
eth_dst=None, eth_dst_mask=None,
ipv6_nd_target=None, icmpv6_type=None,
nw_proto=None,
nw_src=None, nw_dst=None):
match_dict = {}
if in_port is not None:
match_dict['in_port'] = in_port
if vlan is not None:
if vlan.vid == ofp.OFPVID_NONE:
match_dict['vlan_vid'] = int(ofp.OFPVID_NONE)
elif vlan.vid == ofp.OFPVID_PRESENT:
match_dict['vlan_vid'] = (ofp.OFPVID_PRESENT, ofp.OFPVID_PRESENT)
else:
match_dict['vlan_vid'] = vid_present(vlan.vid)
if eth_src is not None:
match_dict['eth_src'] = eth_src
if eth_dst is not None:
if eth_dst_mask is not None:
match_dict['eth_dst'] = (eth_dst, eth_dst_mask)
else:
match_dict['eth_dst'] = eth_dst
if nw_proto is not None:
match_dict['ip_proto'] = nw_proto
if nw_src is not None:
match_dict['ipv4_src'] = _match_ip_masked(nw_src)
if icmpv6_type is not None:
match_dict['icmpv6_type'] = icmpv6_type
if ipv6_nd_target is not None:
match_dict['ipv6_nd_target'] = str(ipv6_nd_target.ip)
if nw_dst is not None:
nw_dst_masked = _match_ip_masked(nw_dst)
if eth_type == ether.ETH_TYPE_ARP:
match_dict['arp_tpa'] = str(nw_dst.ip)
elif eth_type == ether.ETH_TYPE_IP:
match_dict['ipv4_dst'] = nw_dst_masked
else:
match_dict['ipv6_dst'] = nw_dst_masked
if eth_type is not None:
match_dict['eth_type'] = eth_type
return match_dict
[docs]def flowmod(cookie, command, table_id, priority, out_port, out_group,
match_fields, inst, hard_timeout, idle_timeout, flags=0):
return parser.OFPFlowMod(
datapath=None,
cookie=cookie,
command=command,
table_id=table_id,
priority=priority,
out_port=out_port,
out_group=out_group,
match=match_fields,
instructions=inst,
hard_timeout=hard_timeout,
idle_timeout=idle_timeout,
flags=flags)
[docs]def group_act(group_id):
"""Return an action to run a group."""
return parser.OFPActionGroup(group_id)
[docs]def bucket(weight=0, watch_port=ofp.OFPP_ANY,
watch_group=ofp.OFPG_ANY, actions=None):
"""Return a group action bucket with provided actions."""
return parser.OFPBucket(
weight=weight,
watch_port=watch_port,
watch_group=watch_group,
actions=actions)
[docs]def groupmod(datapath=None, type_=ofp.OFPGT_ALL, group_id=0, buckets=None):
"""Modify a group."""
return parser.OFPGroupMod(
datapath,
ofp.OFPGC_MODIFY,
type_,
group_id,
buckets)
[docs]def groupmod_ff(datapath=None, group_id=0, buckets=None):
"""Modify a fast failover group."""
return groupmod(datapath, type_=ofp.OFPGT_FF, group_id=group_id, buckets=buckets)
[docs]def groupadd(datapath=None, type_=ofp.OFPGT_ALL, group_id=0, buckets=None):
"""Add a group."""
return parser.OFPGroupMod(
datapath,
ofp.OFPGC_ADD,
type_,
group_id,
buckets)
[docs]def groupadd_ff(datapath=None, group_id=0, buckets=None):
"""Add a fast failover group."""
return groupadd(datapath, type_=ofp.OFPGT_FF, group_id=group_id, buckets=buckets)
[docs]def groupdel(datapath=None, group_id=ofp.OFPG_ALL):
"""Delete a group (default all groups)."""
return parser.OFPGroupMod(
datapath,
ofp.OFPGC_DELETE,
0,
group_id)
[docs]def meterdel(datapath=None, meter_id=ofp.OFPM_ALL):
"""Delete a meter (default all meters)."""
return parser.OFPMeterMod(
datapath,
ofp.OFPMC_DELETE,
0,
meter_id)
[docs]def meteradd(meter_conf):
"""Add a meter based on YAML configuration."""
class NoopDP(object):
"""Fake DP to be able to use ofctl to parse meter config."""
id = 0
msg = None
ofproto = ofp
ofproto_parser = parser
def send_msg(self, msg):
"""Save msg only."""
self.msg = msg
@staticmethod
def set_xid(msg):
"""Clear msg XID."""
msg.xid = 0
noop_dp = NoopDP()
ofctl.mod_meter_entry(noop_dp, meter_conf, ofp.OFPMC_ADD)
noop_dp.msg.xid = None
noop_dp.msg.datapath = None
return noop_dp.msg
[docs]def controller_pps_meteradd(datapath=None, pps=0):
"""Add a PPS meter towards controller."""
return parser.OFPMeterMod(
datapath=datapath,
command=ofp.OFPMC_ADD,
flags=ofp.OFPMF_PKTPS,
meter_id=ofp.OFPM_CONTROLLER,
bands=[parser.OFPMeterBandDrop(rate=pps)])
[docs]def controller_pps_meterdel(datapath=None):
"""Delete a PPS meter towards controller."""
return parser.OFPMeterMod(
datapath=datapath,
command=ofp.OFPMC_DELETE,
flags=ofp.OFPMF_PKTPS,
meter_id=ofp.OFPM_CONTROLLER)
[docs]def valve_flowreorder(input_ofmsgs):
"""Reorder flows for better OFA performance."""
# Move all deletes to be first, and add one barrier,
# while preserving order. Platforms that do parallel delete
# will perform better and platforms that don't will have
# at most only one barrier to deal with.
# TODO: further optimizations may be possible - for example,
# reorder adds to be in priority order.
delete_ofmsgs = []
groupadd_ofmsgs = []
meteradd_ofmsgs = []
nondelete_ofmsgs = []
for ofmsg in input_ofmsgs:
if is_flowdel(ofmsg) or is_groupdel(ofmsg) or is_meterdel(ofmsg):
delete_ofmsgs.append(ofmsg)
elif is_groupadd(ofmsg):
# The same group_id may be deleted/added multiple times
# To avoid group_mod_failed/group_exists error, if the
# same group_id is already in groupadd_ofmsgs I replace
# it instead of appending it (the last groupadd in
# input_ofmsgs is the only one sent to the switch)
# TODO: optimize the provisioning to avoid having the
# same group_id multiple times in input_ofmsgs
new_group_id = True
for i, groupadd_ofmsg in enumerate(groupadd_ofmsgs):
if groupadd_ofmsg.group_id == ofmsg.group_id:
groupadd_ofmsgs[i] = ofmsg
new_group_id = False
break
if new_group_id:
groupadd_ofmsgs.append(ofmsg)
elif is_meteradd(ofmsg):
meteradd_ofmsgs.append(ofmsg)
# Is there the risk to receice the same meter_id multiple times?
# Do we need the same logic used for groups?
else:
nondelete_ofmsgs.append(ofmsg)
output_ofmsgs = []
if delete_ofmsgs:
output_ofmsgs.extend(delete_ofmsgs)
output_ofmsgs.append(barrier())
if groupadd_ofmsgs + meteradd_ofmsgs:
output_ofmsgs.extend(groupadd_ofmsgs + meteradd_ofmsgs)
output_ofmsgs.append(barrier())
output_ofmsgs.extend(nondelete_ofmsgs)
return output_ofmsgs
[docs]def group_flood_buckets(ports, untagged):
buckets = []
for port in ports:
out_actions = []
if untagged:
out_actions.append(pop_vlan())
out_actions.append(output_port(port.number))
buckets.append(bucket(actions=out_actions))
return buckets
[docs]def flood_tagged_port_outputs(ports, in_port, exclude_ports=None):
"""Return list of actions necessary to flood to list of tagged ports."""
flood_acts = []
if ports:
for port in ports:
if port == in_port:
if port.hairpin:
flood_acts.append(output_in_port())
continue
if exclude_ports and port in exclude_ports:
continue
flood_acts.append(output_port(port.number))
return flood_acts
[docs]def flood_untagged_port_outputs(ports, in_port, exclude_ports=None):
"""Return list of actions necessary to flood to list of untagged ports."""
flood_acts = []
if ports:
flood_acts.append(pop_vlan())
flood_acts.extend(flood_tagged_port_outputs(
ports, in_port, exclude_ports=exclude_ports))
return flood_acts
[docs]def faucet_config(datapath=None):
"""Return switch config for FAUCET."""
return parser.OFPSetConfig(datapath, ofp.OFPC_FRAG_NORMAL, 0)
[docs]def faucet_async(datapath=None):
"""Return async message config for FAUCET."""
packet_in_mask = 1 << ofp.OFPR_ACTION
port_status_mask = (
1 << ofp.OFPPR_ADD | 1 << ofp.OFPPR_DELETE | 1 << ofp.OFPPR_MODIFY)
flow_removed_mask = (
1 << ofp.OFPRR_IDLE_TIMEOUT | 1 << ofp.OFPRR_HARD_TIMEOUT)
return parser.OFPSetAsync(
datapath,
[packet_in_mask, packet_in_mask],
[port_status_mask, port_status_mask],
[flow_removed_mask, flow_removed_mask])
[docs]def gauge_async(datapath=None):
"""Return async message config for Gauge."""
packet_in_mask = 0
port_status_mask = (
1 << ofp.OFPPR_ADD | 1 << ofp.OFPPR_DELETE | 1 << ofp.OFPPR_MODIFY)
flow_removed_mask = 0
return parser.OFPSetAsync(
datapath,
[packet_in_mask, packet_in_mask],
[port_status_mask, port_status_mask],
[flow_removed_mask, flow_removed_mask])
[docs]def desc_stats_request(datapath=None):
"""Query switch description."""
return parser.OFPDescStatsRequest(datapath, 0)