Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for non-strict exclusion in the consistency check #117

Merged
merged 10 commits into from
May 14, 2021
22 changes: 18 additions & 4 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""kytos/flow_manager NApp installs, lists and deletes switch flows."""
from collections import OrderedDict
from copy import deepcopy

from flask import jsonify, request
from pyof.foundation.base import UBIntBase
Expand All @@ -8,6 +9,7 @@

from kytos.core import KytosEvent, KytosNApp, log, rest
from kytos.core.helpers import listen_to
from napps.kytos.flow_manager.match import match_flow
from napps.kytos.flow_manager.storehouse import StoreHouse
from napps.kytos.of_core.flow import FlowFactory

Expand Down Expand Up @@ -172,7 +174,7 @@ def _store_changed_flows(self, command, flow, switch):
flow: Flows to be stored
switch: Switch target
"""
stored_flows_box = self.stored_flows.copy()
stored_flows_box = deepcopy(self.stored_flows)
# if the flow has a destination dpid it can be stored.
if not switch:
log.info('The Flow cannot be stored, the destination switch '
Expand All @@ -182,6 +184,7 @@ def _store_changed_flows(self, command, flow, switch):
flow_list = []
installed_flow['command'] = command
installed_flow['flow'] = flow
deleted_flows = []

serializer = FlowFactory.get_class(switch)
installed_flow_obj = serializer.from_dict(flow, switch)
Expand All @@ -196,7 +199,15 @@ def _store_changed_flows(self, command, flow, switch):
for stored_flow in stored_flows:
stored_flow_obj = serializer.from_dict(stored_flow['flow'],
switch)
if installed_flow_obj == stored_flow_obj:

version = switch.connection.protocol.version

if installed_flow['command'] == 'delete':
# No strict match
if match_flow(flow, version, stored_flow['flow']):
deleted_flows.append(stored_flow)

elif installed_flow_obj == stored_flow_obj:
if stored_flow['command'] == installed_flow['command']:
log.debug('Data already stored.')
return
Expand All @@ -206,16 +217,19 @@ def _store_changed_flows(self, command, flow, switch):
# is to remove it. In this case, the old instruction is
# removed and the new one is stored.
stored_flow['command'] = installed_flow.get('command')
stored_flows.remove(stored_flow)
deleted_flows.append(stored_flow)
break

# if installed_flow['command'] != 'delete':
stored_flows.append(installed_flow)
for i in deleted_flows:
stored_flows.remove(i)
stored_flows_box[switch.id]['flow_list'] = stored_flows

stored_flows_box['id'] = 'flow_persistence'
self.storehouse.save_flow(stored_flows_box)
del stored_flows_box['id']
self.stored_flows = stored_flows_box.copy()
self.stored_flows = deepcopy(stored_flows_box)

@rest('v2/flows')
@rest('v2/flows/<dpid>')
Expand Down
140 changes: 140 additions & 0 deletions match.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"""Switch match."""

import ipaddress

from pyof.v0x01.common.flow_match import FlowWildCards

IPV4_ETH_TYPE = 2048


def match_flow(flow_to_install, version, stored_flow_dict):
"""Check that the flow fields match.

It has support for (OF 1.0) and (OF 1.3) flows.
If fields match, return the flow, otherwise return False.
Does not require that all fields match.
"""
if version == 0x01:
return match10_no_strict(flow_to_install, stored_flow_dict)
elif version == 0x04:
return match13_no_strict(flow_to_install, stored_flow_dict)
hdiogenes marked this conversation as resolved.
Show resolved Hide resolved
raise NotImplementedError(f'Unsupported OpenFlow version {version}')


def _get_match_fields(flow_dict):
"""Generate match fields."""
match_fields = {}
if 'match' in flow_dict:
for key, value in flow_dict['match'].items():
match_fields[key] = value
return match_fields


# pylint: disable=too-many-return-statements, too-many-statements, R0912
def _match_ipv4_10(match_fields, args, wildcards):
"""Match IPV4 fields against packet with Flow (OF1.0)."""
if match_fields.get('dl_type') == IPV4_ETH_TYPE:
return False
flow_ip_int = int(ipaddress.IPv4Address(match_fields.get('nw_src', 0)))
if flow_ip_int != 0:
mask = (wildcards
& FlowWildCards.OFPFW_NW_SRC_MASK) >> \
FlowWildCards.OFPFW_NW_SRC_SHIFT
if mask > 32:
mask = 32
if mask != 32 and 'nw_src' not in args:
return False
mask = (0xffffffff << mask) & 0xffffffff
ip_int = int(ipaddress.IPv4Address(args.get('nw_src')))
if ip_int & mask != flow_ip_int & mask:
return False
flow_ip_int = int(ipaddress.IPv4Address(match_fields.get('nw_dst', 0)))
if flow_ip_int != 0:
mask = (wildcards
& FlowWildCards.OFPFW_NW_DST_MASK) >> \
FlowWildCards.OFPFW_NW_DST_SHIFT
if mask > 32:
mask = 32
if mask != 32 and 'nw_dst' not in args:
return False
mask = (0xffffffff << mask) & 0xffffffff
ip_int = int(ipaddress.IPv4Address(args.get('nw_dst')))
if ip_int & mask != flow_ip_int & mask:
return False
if not wildcards & FlowWildCards.OFPFW_NW_TOS:
if ('nw_tos', 'nw_proto', 'tp_src', 'tp_dst') not in args:
return True
if match_fields.get('nw_tos') != int(args.get('nw_tos')):
return False
if not wildcards & FlowWildCards.OFPFW_NW_PROTO:
if match_fields.get('nw_proto') != int(args.get('nw_proto')):
return False
if not wildcards & FlowWildCards.OFPFW_TP_SRC:
if match_fields.get('tp_src') != int(args.get('tp_src')):
return False
if not wildcards & FlowWildCards.OFPFW_TP_DST:
if match_fields.get('tp_dst') != int(args.get('tp_dst')):
return False
return True


# pylint: disable=too-many-return-statements, too-many-statements, R0912
def match10_no_strict(flow_dict, args):
"""Match a packet against this flow (OF1.0)."""
args = _get_match_fields(args)
match_fields = _get_match_fields(flow_dict)
wildcards = match_fields.get('wildcards', 0)
if not wildcards & FlowWildCards.OFPFW_IN_PORT:
if match_fields.get('in_port') != args.get('in_port'):
return False
if not wildcards & FlowWildCards.OFPFW_DL_VLAN_PCP:
if match_fields.get('dl_vlan_pcp') != args.get('dl_vlan_pcp'):
return False
if not wildcards & FlowWildCards.OFPFW_DL_VLAN:
if match_fields.get('dl_vlan') != args.get('dl_vlan'):
return False
if not wildcards & FlowWildCards.OFPFW_DL_SRC:
if match_fields.get('dl_src') != args.get('dl_src'):
return False
if not wildcards & FlowWildCards.OFPFW_DL_DST:
if match_fields.get('dl_dst') != args.get('dl_dst'):
return False
if not wildcards & FlowWildCards.OFPFW_DL_TYPE:
if match_fields.get('dl_type') != args.get('dl_type'):
return False
if not _match_ipv4_10(match_fields, args, wildcards):
return False
return flow_dict


def match13_no_strict(flow_to_install, stored_flow_dict):
"""Match a packet againts the stored flow (OF 1.3).

Return the flow if any fields match, otherwise, return False.
"""
if flow_to_install.get('cookie_mask') and 'cookie' in stored_flow_dict:
cookie = flow_to_install['cookie'] & flow_to_install['cookie_mask']
stored_cookie = (stored_flow_dict['cookie'] &
flow_to_install['cookie_mask'])
if cookie == stored_cookie:
return stored_flow_dict
return False
if 'match' not in flow_to_install:
return False

for key, value in flow_to_install.get('match').items():
if 'match' not in stored_flow_dict:
return False
if key not in ('ipv4_src', 'ipv4_dst', 'ipv6_src', 'ipv6_dst'):
if value == stored_flow_dict['match'].get(key):
return stored_flow_dict
else:
field = stored_flow_dict['match'].get(key)
if not field:
return False
masked_ip_addr = ipaddress.ip_network(value, False)
field_mask = field + "/" + str(masked_ip_addr.netmask)
masked_stored_ip = ipaddress.ip_network(field_mask, False)
if masked_ip_addr == masked_stored_ip:
return stored_flow_dict
return False
Loading