Source code for faucet.valve_acl

"""Compose ACLs on ports."""

# 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--2019 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.

from faucet import valve_of
from faucet import valve_packet
from faucet.valve_manager_base import ValveManagerBase
from faucet.conf import InvalidConfigError


[docs] def push_vlan(acl_table, vlan_vid): """Push a VLAN tag with optional selection of eth type.""" vid = vlan_vid vlan_eth_type = None if isinstance(vlan_vid, dict): vid = vlan_vid["vid"] if "eth_type" in vlan_vid: vlan_eth_type = vlan_vid["eth_type"] if vlan_eth_type is None: return valve_of.push_vlan_act(acl_table, vid) return valve_of.push_vlan_act(acl_table, vid, eth_type=vlan_eth_type)
[docs] def build_ordered_output_actions( acl_table, output_list, tunnel_rules=None, source_id=None ): """Build actions from ordered ACL output list""" output_actions = [] output_ports = [] output_ofmsgs = [] output_inst = [] for action in output_list: for key, value in action.items(): if key == "pop_vlans": for _ in range(value): output_actions.append(valve_of.pop_vlan()) if key == "vlan_vid": output_actions.extend(push_vlan(acl_table, value)) if key == "swap_vid": output_actions.append(acl_table.set_vlan_vid(value)) if key == "vlan_vids": for vlan_vid in value: output_actions.extend(push_vlan(acl_table, vlan_vid)) if key == "set_fields": for set_field in value: output_actions.append(acl_table.set_field(**set_field)) if key == "port": output_ports.append(value) output_actions.append(valve_of.output_port(value)) if key == "ports": for output_port in value: output_ports.append(output_port) output_actions.append(valve_of.output_port(output_port)) if key == "failover": group_id = value["group_id"] buckets = [] for port in value["ports"]: buckets.append( valve_of.bucket( watch_port=port, actions=[valve_of.output_port(port)] ) ) output_ofmsgs.extend( valve_of.groupadd_ff(group_id=group_id, buckets=buckets) ) output_actions.append(valve_of.group_act(group_id=group_id)) if key == "tunnel" and tunnel_rules and source_id is not None: source_rule = tunnel_rules[value][source_id] _, tunnel_actions, tunnel_ofmsgs, tunnel_inst = build_output_actions( acl_table, source_rule ) output_actions.extend(tunnel_actions) output_ofmsgs.extend(tunnel_ofmsgs) output_inst.extend(tunnel_inst) if key == "goto": output_inst.append(valve_of.goto_table_id(value)) return (output_ports, output_actions, output_ofmsgs, output_inst)
[docs] def rewrite_vlan(acl_table, output_dict): """Implement actions to rewrite VLAN headers.""" vlan_actions = [] if "pop_vlans" in output_dict: for _ in range(output_dict["pop_vlans"]): vlan_actions.append(valve_of.pop_vlan()) # if vlan tag is specified, push it. if "vlan_vid" in output_dict: vlan_actions.extend(push_vlan(acl_table, output_dict["vlan_vid"])) # swap existing VID elif "swap_vid" in output_dict: vlan_actions.append(acl_table.set_vlan_vid(output_dict["swap_vid"])) # or, if a list, push them all (all with type Q). elif "vlan_vids" in output_dict: for vlan_vid in output_dict["vlan_vids"]: vlan_actions.extend(push_vlan(acl_table, vlan_vid)) return vlan_actions
[docs] def build_ct_actions(acl_table, ct_dict): """Build conntrack action from ACL rule""" if "clear" in ct_dict and ct_dict["clear"]: return valve_of.ct_clear() ct_actions = [] if "nat" in ct_dict: nat_args = { "flags": ct_dict["nat"].get("flags", 0), "range_ipv4_min": ct_dict["nat"].get("range_ipv4_min", ""), "range_ipv4_max": ct_dict["nat"].get("range_ipv4_max", ""), "range_ipv6_min": ct_dict["nat"].get("range_ipv6_min", ""), "range_ipv6_max": ct_dict["nat"].get("range_ipv6_max", ""), "range_proto_min": ct_dict["nat"].get("range_proto_min", None), "range_proto_max": ct_dict["nat"].get("range_proto_max", None), } ct_actions.append(valve_of.ct_nat(**nat_args)) ct_args = { "flags": ct_dict.get("flags", 0), "actions": ct_actions, "alg": ct_dict.get("alg", 0), "recirc_table": ct_dict.get("table"), "zone_ofs_nbits": ct_dict.get("zone"), "zone_src": ct_dict.get("zone_src", None), } return valve_of.ct(**ct_args)
[docs] def build_output_actions(acl_table, output_dict, tunnel_rules=None, source_id=None): """Implement actions to alter packet/output.""" if isinstance(output_dict, (list, tuple)): return build_ordered_output_actions( acl_table, output_dict, tunnel_rules, source_id ) output_actions = [] output_inst = [] output_port = None ofmsgs = [] # rewrite any VLAN headers first always vlan_actions = rewrite_vlan(acl_table, output_dict) if vlan_actions: output_actions.extend(vlan_actions) if "set_fields" in output_dict: for set_field in output_dict["set_fields"]: output_actions.append(acl_table.set_field(**set_field)) if "port" in output_dict: output_port = output_dict["port"] output_actions.append(valve_of.output_port(output_port)) if "ports" in output_dict: for output_port in output_dict["ports"]: output_actions.append(valve_of.output_port(output_port)) if "failover" in output_dict: failover = output_dict["failover"] group_id = failover["group_id"] buckets = [] for port in failover["ports"]: buckets.append( valve_of.bucket(watch_port=port, actions=[valve_of.output_port(port)]) ) ofmsgs.extend(valve_of.groupadd_ff(group_id=group_id, buckets=buckets)) output_actions.append(valve_of.group_act(group_id=group_id)) if "tunnel" in output_dict and tunnel_rules and source_id is not None: tunnel_id = output_dict["tunnel"] source_rule = tunnel_rules[tunnel_id][source_id] _, tunnel_actions, tunnel_ofmsgs, tunnel_inst = build_output_actions( acl_table, source_rule ) output_actions.extend(tunnel_actions) tunnel_ofmsgs.extend(tunnel_ofmsgs) output_inst.extend(tunnel_inst) if "goto" in output_dict: output_inst.append(valve_of.goto_table_id(output_dict["goto"])) return (output_port, output_actions, ofmsgs, output_inst)
[docs] def build_acl_entry( acl_table, rule_conf, meters, acl_allow_inst, acl_force_port_vlan_inst, port_num=None, vlan_vid=None, tunnel_rules=None, source_id=None, ): # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-branches,too-many-statements """Build flow/groupmods for one ACL rule entry.""" acl_inst = [] acl_act = [] acl_match_dict = {} acl_ofmsgs = [] acl_cookie = None allow_inst = acl_allow_inst for attrib, attrib_value in rule_conf.items(): # if attrib == 'in_port': # continue if attrib == "cookie": acl_cookie = attrib_value continue if attrib == "description": continue if attrib == "actions": allow = False allow_specified = False if "allow" in attrib_value: allow_specified = True if attrib_value["allow"] == 1: allow = True if "force_port_vlan" in attrib_value: if attrib_value["force_port_vlan"] == 1: allow_inst = acl_force_port_vlan_inst if "meter" in attrib_value: meter_name = attrib_value["meter"] acl_inst.append(valve_of.apply_meter(meters[meter_name].meter_id)) if "mirror" in attrib_value: port_no = attrib_value["mirror"] acl_act.append(valve_of.output_port(port_no)) if not allow_specified: allow = True if "output" in attrib_value: ( output_port, output_actions, output_ofmsgs, output_inst, ) = build_output_actions( acl_table, attrib_value["output"], tunnel_rules, source_id ) acl_act.extend(output_actions) acl_ofmsgs.extend(output_ofmsgs) acl_inst.extend(output_inst) # if port specified, output packet now and exit pipeline. if not allow and output_port is not None: continue if "ct" in attrib_value: ct_action = build_ct_actions(acl_table, attrib_value["ct"]) acl_act.append(ct_action) if allow: acl_inst.extend(allow_inst) else: acl_match_dict[attrib] = attrib_value if port_num is not None: # This overwrites the `in_port` match if it is specified in the ACL config acl_match_dict["in_port"] = port_num if vlan_vid is not None: # This overwrites the `vlan_vid` match if it is specified in the ACL config acl_match_dict["vlan_vid"] = valve_of.vid_present(vlan_vid) try: acl_match = valve_of.match_from_dict(acl_match_dict) except TypeError as type_error: raise InvalidConfigError("invalid match type in ACL") from type_error if acl_act: acl_inst.append(valve_of.apply_actions(acl_act)) return (acl_match, acl_inst, acl_cookie, acl_ofmsgs)
[docs] def build_tunnel_ofmsgs( rule_conf, acl_table, priority, port_num=None, vlan_vid=None, flowdel=False, reverse=False, ): # pylint: disable=too-many-arguments,too-many-positional-arguments """Build a specific tunnel only ofmsgs""" ofmsgs = [] acl_inst = [] acl_match = [] acl_match_dict = {} _, output_actions, output_ofmsgs, output_inst = build_output_actions( acl_table, rule_conf ) ofmsgs.extend(output_ofmsgs) acl_inst.extend(output_inst) acl_inst.append(valve_of.apply_actions(output_actions)) if port_num is not None: acl_match_dict["in_port"] = port_num if vlan_vid is not None: acl_match_dict["vlan_vid"] = valve_of.vid_present(vlan_vid) if reverse: acl_match_dict["vlan_pcp"] = valve_of.PCP_TUNNEL_REVERSE_DIRECTION_FLAG else: acl_match_dict["vlan_pcp"] = valve_of.PCP_TUNNEL_FLAG try: acl_match = valve_of.match_from_dict(acl_match_dict) except TypeError as type_error: raise InvalidConfigError("invalid match type in ACL") from type_error flowmod = acl_table.flowmod(acl_match, priority=priority, inst=tuple(acl_inst)) if flowdel: ofmsgs.append( acl_table.flowdel(match=acl_match, priority=priority, strict=False) ) ofmsgs.append(flowmod) return ofmsgs
[docs] def build_rule_ofmsgs( rule_conf, acl_table, acl_allow_inst, acl_force_port_vlan_inst, highest_priority, acl_rule_priority, meters, exact_match, port_num=None, vlan_vid=None, tunnel_rules=None, source_id=None, flowdel=False, ): # pylint: disable=too-many-arguments,too-many-positional-arguments """Build an ACL rule and return OFMSGs""" ofmsgs = [] acl_match, acl_inst, acl_cookie, acl_ofmsgs = build_acl_entry( acl_table, rule_conf, meters, acl_allow_inst, acl_force_port_vlan_inst, port_num, vlan_vid, tunnel_rules, source_id, ) ofmsgs.extend(acl_ofmsgs) priority = acl_rule_priority if exact_match: priority = highest_priority flowmod = acl_table.flowmod( acl_match, priority=priority, inst=tuple(acl_inst), cookie=acl_cookie ) if flowdel: ofmsgs.append(acl_table.flowdel(match=acl_match, priority=priority)) ofmsgs.append(flowmod) return ofmsgs
[docs] def build_acl_ofmsgs( acls, acl_table, acl_allow_inst, acl_force_port_vlan_inst, highest_priority, meters, exact_match, port_num=None, vlan_vid=None, tunnel_rules=None, source_id=None, flowdel=False, ): # pylint: disable=too-many-arguments,too-many-positional-arguments """Build flow/groupmods for all entries in an ACL.""" ofmsgs = [] acl_rule_priority = highest_priority for acl in acls: for rule_conf in acl.rules: ofmsgs.extend( build_rule_ofmsgs( rule_conf, acl_table, acl_allow_inst, acl_force_port_vlan_inst, highest_priority, acl_rule_priority, meters, exact_match, port_num, vlan_vid, tunnel_rules=tunnel_rules, source_id=source_id, flowdel=flowdel, ) ) acl_rule_priority -= 1 return ofmsgs
[docs] def build_acl_port_of_msgs(acl, vid, port_num, acl_table, goto_table, priority): """A Helper function for building Openflow Mod Messages for Port ACLs""" ofmsgs = None if acl.rules: ofmsgs = build_acl_ofmsgs( [acl], acl_table, [valve_of.goto_table(goto_table)], [valve_of.goto_table(goto_table)], priority, acl.meter, acl.exact_match, vlan_vid=vid, port_num=port_num, ) return ofmsgs
[docs] def add_mac_address_to_match(match, eth_src): """Add or change the value of a match type""" # NOTE: This function has been created to work around for # OFPMatch.set_dl_src() not storing persistent changes if not eth_src: return match dict_match = dict(match.items()) dict_match["eth_src"] = eth_src return valve_of.match_from_dict(dict_match)
[docs] class ValveAclManager(ValveManagerBase): """Handle installation of ACLs on a DP""" def __init__( self, port_acl_table, vlan_acl_table, egress_acl_table, pipeline, meters, dp_acls=None, ): # pylint: disable=too-many-arguments,too-many-positional-arguments self.dp_acls = dp_acls self.port_acl_table = port_acl_table self.vlan_acl_table = vlan_acl_table self.egress_acl_table = egress_acl_table self.pipeline = pipeline self.acl_priority = self._FILTER_PRIORITY self.override_priority = self.acl_priority + 1 self.auth_priority = self._HIGH_PRIORITY self.low_priority = self.auth_priority - 1 self.meters = meters
[docs] def initialise_tables(self): """Install dp acls if configured""" ofmsgs = [] if self.dp_acls: acl_allow_inst = self.pipeline.accept_to_vlan() acl_force_port_vlan_inst = self.pipeline.accept_to_l2_forwarding() ofmsgs.extend( build_acl_ofmsgs( self.dp_acls, self.port_acl_table, acl_allow_inst, acl_force_port_vlan_inst, self.acl_priority, self.meters, False, ) ) return ofmsgs
def _port_acls_allowed(self, port): return not self.dp_acls and not port.output_only and self.port_acl_table
[docs] def add_port(self, port): """Install port acls if configured""" ofmsgs = [] if self._port_acls_allowed(port): in_port_match = self.port_acl_table.match(in_port=port.number) acl_allow_inst = self.pipeline.accept_to_vlan() acl_force_port_vlan_inst = self.pipeline.accept_to_l2_forwarding() if port.acls_in: ofmsgs.extend( build_acl_ofmsgs( port.acls_in, self.port_acl_table, acl_allow_inst, acl_force_port_vlan_inst, self.acl_priority, self.meters, port.acls_in[0].exact_match, port_num=port.number, ) ) elif not port.dot1x: ofmsgs.append( self.port_acl_table.flowmod( in_port_match, priority=self.acl_priority, inst=tuple(acl_allow_inst), ) ) return ofmsgs
[docs] def del_port(self, port): ofmsgs = [] if self._port_acls_allowed(port): in_port_match = self.port_acl_table.match(in_port=port.number) ofmsgs.append(self.port_acl_table.flowdel(in_port_match, self.acl_priority)) return ofmsgs
[docs] def cold_start_port(self, port): """Reload acl for a port by deleting existing rules and calling add_port""" ofmsgs = [] ofmsgs.extend(self.del_port(port)) ofmsgs.extend(self.add_port(port)) return ofmsgs
[docs] def del_vlan(self, vlan): """Remove VLAN ACLs if configured.""" ofmsgs = [] for table in (self.vlan_acl_table, self.egress_acl_table): if table is not None: ofmsgs.append(table.flowdel(match=table.match(vlan=vlan))) return ofmsgs
[docs] def add_vlan(self, vlan, cold_start): """Install VLAN ACL if configured.""" ofmsgs = [] if vlan.acls_in: acl_allow_inst = self.pipeline.accept_to_classification() acl_force_port_vlan_inst = self.pipeline.accept_to_l2_forwarding() ofmsgs = build_acl_ofmsgs( vlan.acls_in, self.vlan_acl_table, acl_allow_inst, acl_force_port_vlan_inst, self.acl_priority, self.meters, vlan.acls_in[0].exact_match, vlan_vid=vlan.vid, ) if self.egress_acl_table is not None: egress_acl_allow_inst = self.pipeline.accept_to_egress() if vlan.acls_out: ofmsgs.extend( build_acl_ofmsgs( vlan.acls_out, self.egress_acl_table, egress_acl_allow_inst, egress_acl_allow_inst, self.acl_priority, self.meters, vlan.acls_out[0].exact_match, vlan_vid=vlan.vid, ) ) else: ofmsgs.append( self.egress_acl_table.flowmod( self.egress_acl_table.match(vlan=vlan), priority=self.acl_priority, inst=tuple(egress_acl_allow_inst), ) ) return ofmsgs
[docs] def add_authed_mac(self, port_num, mac): """Add authed mac address""" return [ self.port_acl_table.flowmod( self.port_acl_table.match(in_port=port_num, eth_src=mac), priority=self.auth_priority, inst=tuple(self.pipeline.accept_to_vlan()), ) ]
[docs] def del_authed_mac(self, port_num, mac=None, strict=True): """remove authed mac address""" if mac: return [ self.port_acl_table.flowdel( self.port_acl_table.match(in_port=port_num, eth_src=mac), priority=self.auth_priority, strict=strict, ) ] return [ self.port_acl_table.flowdel( self.port_acl_table.match(in_port=port_num), priority=self.auth_priority, strict=strict, ) ]
[docs] def del_port_acl(self, acl, port_num, mac=None): """Delete ACL rules for Port""" def convert_to_flow_del(ofp_flowmods): flowdels = [] for flowmod in ofp_flowmods: flowdels.append( self.port_acl_table.flowdel( match=flowmod.match, priority=flowmod.priority ) ) return flowdels pipeline_vlan_table = self.pipeline.vlan_table flowmods = build_acl_port_of_msgs( acl, None, port_num, self.port_acl_table, pipeline_vlan_table, self.auth_priority, ) for flow in flowmods: flow.match = add_mac_address_to_match(flow.match, mac) return convert_to_flow_del(flowmods)
[docs] def add_port_acl(self, acl, port_num, mac=None): """Create ACL openflow rules for Port""" pipeline_vlan_table = self.pipeline.vlan_table flowmods = build_acl_port_of_msgs( acl, None, port_num, self.port_acl_table, pipeline_vlan_table, self.auth_priority, ) for flow in flowmods: flow.match = add_mac_address_to_match(flow.match, mac) return flowmods
[docs] def create_dot1x_flow_pair(self, port_num, nfv_sw_port_num, mac): """Create dot1x flow pair""" ofmsgs = [ self.port_acl_table.flowmod( match=self.port_acl_table.match( in_port=port_num, eth_type=valve_packet.ETH_EAPOL ), priority=self.override_priority, inst=( valve_of.apply_actions( [ self.port_acl_table.set_field(eth_dst=mac), valve_of.output_port(nfv_sw_port_num), ] ), ), ), self.port_acl_table.flowmod( match=self.port_acl_table.match( in_port=nfv_sw_port_num, eth_type=valve_packet.ETH_EAPOL, eth_src=mac, ), priority=self.override_priority, inst=( valve_of.apply_actions( [ self.port_acl_table.set_field( eth_src=valve_packet.EAPOL_ETH_DST ), valve_of.output_port(port_num), ] ), ), ), ] return ofmsgs
[docs] def del_dot1x_flow_pair(self, port_num, nfv_sw_port_num, mac): """Deletes dot1x flow pair""" ofmsgs = [ self.port_acl_table.flowdel( match=self.port_acl_table.match( in_port=nfv_sw_port_num, eth_type=valve_packet.ETH_EAPOL, eth_src=mac, ), priority=self.override_priority, ), self.port_acl_table.flowdel( match=self.port_acl_table.match( in_port=port_num, eth_type=valve_packet.ETH_EAPOL ), priority=self.override_priority, ), ] return ofmsgs
[docs] def create_mab_flow(self, port_num, nfv_sw_port_num, mac): """ Create MAB ACL for sending IP Activity to Chewie NFV Returns flowmods to send all IP traffic to Chewie Args: port_num (int): Number of port in nfv_sw_port_num(int): Number of port out mac(str): MAC address of the valve/port combo """ return self.port_acl_table.flowmod( match=self.port_acl_table.match( in_port=port_num, eth_type=valve_of.ether.ETH_TYPE_IP, nw_proto=valve_of.inet.IPPROTO_UDP, udp_src=68, udp_dst=67, ), priority=self.low_priority, inst=( valve_of.apply_actions( [ self.port_acl_table.set_field(eth_dst=mac), valve_of.output_port(nfv_sw_port_num), ] ), ), )
[docs] def del_mab_flow(self, port_num, _nfv_sw_port_num, _mac): """ Remove MAB ACL for sending IP Activity to Chewie NFV Returns flowmods to send all IP traffic to Chewie Args: port_num (int): Number of port in _nfv_sw_port_num(int): Number of port out _mac(str): MAC address of the valve/port combo """ return [ self.port_acl_table.flowdel( match=self.port_acl_table.match( in_port=port_num, eth_type=valve_of.ether.ETH_TYPE_IP, nw_proto=valve_of.inet.IPPROTO_UDP, udp_src=68, udp_dst=67, ), priority=self.low_priority, strict=True, ) ]
[docs] def build_tunnel_rules_ofmsgs(self, source_id, tunnel_id, acl): """Build a tunnel only generated rule""" ofmsgs = [] acl_table = self.port_acl_table priority = self.override_priority reverse = acl.tunnel_dests[tunnel_id]["reverse"] ofmsgs.extend( build_tunnel_ofmsgs( acl.dyn_tunnel_rules[tunnel_id][source_id], acl_table, priority, None, tunnel_id, flowdel=True, reverse=reverse, ) ) return ofmsgs
[docs] def build_reverse_tunnel_rules_ofmsgs(self, source_id, tunnel_id, acl): """Build a (reverse) tunnel only generated rule""" ofmsgs = [] acl_table = self.port_acl_table priority = self.override_priority if acl.requires_reverse_tunnel(tunnel_id): ofmsgs.extend( build_tunnel_ofmsgs( acl.dyn_reverse_tunnel_rules[tunnel_id][source_id], acl_table, priority, None, tunnel_id, flowdel=True, reverse=True, ) ) return ofmsgs
[docs] def build_tunnel_acl_rule_ofmsgs(self, source_id, tunnel_id, acl): """Build a rule of an ACL that contains a tunnel""" ofmsgs = [] acl_allow_inst = self.pipeline.accept_to_vlan() acl_table = self.port_acl_table rules = acl.get_tunnel_rules(tunnel_id) acl_force_port_vlan_inst = self.pipeline.accept_to_l2_forwarding() if self.dp_acls and acl in self.dp_acls: ofmsgs.extend( build_acl_ofmsgs( self.dp_acls, acl_table, acl_allow_inst, acl_force_port_vlan_inst, self.acl_priority, self.meters, False, tunnel_rules=acl.dyn_tunnel_rules, source_id=source_id, flowdel=True, ) ) else: for rule_conf in rules: rule_index = acl.rules.index(rule_conf) priority = self.acl_priority - rule_index in_port = acl.tunnel_sources[source_id]["port"] ofmsgs.extend( build_rule_ofmsgs( rule_conf, acl_table, acl_allow_inst, acl_force_port_vlan_inst, self.acl_priority, priority, self.meters, acl.exact_match, in_port, None, acl.dyn_tunnel_rules, source_id, flowdel=True, ) ) return ofmsgs
[docs] def add_meters(self, added_meters): """Add new meters.""" ofmsgs = [] if added_meters: for added_meter in added_meters: ofmsgs.append( valve_of.meteradd(self.meters.get(added_meter).entry, command=0) ) return ofmsgs
[docs] def del_meters(self, deleted_meters): ofmsgs = [] if deleted_meters: deleted_meter_ids = [ self.meters[meter_key].meter_id for meter_key in deleted_meters ] ofmsgs.extend( [ valve_of.meterdel(deleted_meter_id) for deleted_meter_id in deleted_meter_ids ] ) return ofmsgs