"""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--2018 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 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_output_actions(acl_table, output_dict):
"""Implement actions to alter packet/output."""
output_actions = []
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.append(valve_of.groupdel(group_id=group_id))
ofmsgs.append(valve_of.groupadd_ff(group_id=group_id, buckets=buckets))
output_actions.append(valve_of.group_act(group_id=group_id))
return (output_port, output_actions, ofmsgs)
# TODO: change this, maybe this can be rewritten easily
# possibly replace with a class for ACLs
[docs]def build_acl_entry(acl_table, rule_conf, meters,
acl_allow_inst, acl_force_port_vlan_inst,
port_num=None, vlan_vid=None):
"""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 = build_output_actions(
acl_table, attrib_value['output'])
acl_act.extend(output_actions)
acl_ofmsgs.extend(output_ofmsgs)
# if port specified, output packet now and exit pipeline.
if not allow and output_port is not None:
continue
if allow:
acl_inst.extend(allow_inst)
else:
acl_match_dict[attrib] = attrib_value
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)
try:
acl_match = valve_of.match_from_dict(acl_match_dict)
except TypeError:
raise InvalidConfigError('invalid type in ACL')
if acl_act:
acl_inst.append(valve_of.apply_actions(acl_act))
return (acl_match, acl_inst, acl_cookie, acl_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):
"""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:
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)
ofmsgs.extend(acl_ofmsgs)
if exact_match:
flowmod = acl_table.flowmod(
acl_match, priority=highest_priority, inst=acl_inst, cookie=acl_cookie)
else:
flowmod = acl_table.flowmod(
acl_match, priority=acl_rule_priority, inst=acl_inst, cookie=acl_cookie)
ofmsgs.append(flowmod)
acl_rule_priority -= 1
return ofmsgs
[docs]class ValveAclManager(ValveManagerBase):
"""Handle installation of ACLs on a DP"""
def __init__(self, port_acl_table, vlan_acl_table, pipeline, meters,
dp_acls=None):
self.dp_acls = dp_acls
self.port_acl_table = port_acl_table
self.vlan_acl_table = vlan_acl_table
self.pipeline = pipeline
self.acl_priority = self._FILTER_PRIORITY
self.auth_priority = self._HIGH_PRIORITY
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
[docs] def add_port(self, port):
"""Install port acls if configured"""
ofmsgs = []
if self.port_acl_table is None or self.dp_acls is not None\
or port.output_only:
return ofmsgs
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=acl_allow_inst))
return ofmsgs
[docs] def cold_start_port(self, port):
"""Reload acl for a port by deleting existing rules and calling
add_port"""
ofmsgs = []
in_port_match = self.port_acl_table.match(in_port=port.number)
ofmsgs.append(self.port_acl_table.flowdel(in_port_match))
ofmsgs.extend(self.add_port(port))
return ofmsgs
[docs] def add_vlan(self, vlan):
"""Install vlan acls 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)
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=self.pipeline.accept_to_vlan())]
[docs] def del_authed_mac(self, port_num, mac=None):
"""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=True)]
return [self.port_acl_table.flowdel(
self.port_acl_table.match(in_port=port_num),
priority=self.auth_priority,
strict=True)]
[docs] def create_dot1x_flow_pair(self, dot1x_port, nfv_sw_port, mac):
"""Create dot1x flow pair"""
ofmsgs = [
self.port_acl_table.flowmod(
match=self.port_acl_table.match(
in_port=dot1x_port.number,
eth_type=valve_packet.ETH_EAPOL),
priority=self.acl_priority,
inst=[valve_of.apply_actions([
self.port_acl_table.set_field(eth_dst=mac),
valve_of.output_port(nfv_sw_port.number)])],
),
self.port_acl_table.flowmod(
match=self.port_acl_table.match(
in_port=nfv_sw_port.number,
eth_type=valve_packet.ETH_EAPOL,
eth_src=mac),
priority=self.acl_priority,
inst=[valve_of.apply_actions([
self.port_acl_table.set_field(
eth_src=valve_packet.EAPOL_ETH_DST),
valve_of.output_port(dot1x_port.number)
])],
)
]
return ofmsgs
[docs] def del_dot1x_flow_pair(self, dot1x_port, nfv_sw_port, mac):
"""Deletes dot1x flow pair"""
ofmsgs = [
self.port_acl_table.flowdel(
match=self.port_acl_table.match(
in_port=nfv_sw_port.number,
eth_type=valve_packet.ETH_EAPOL,
eth_src=mac),
priority=self.acl_priority,
),
self.port_acl_table.flowdel(
match=self.port_acl_table.match(
in_port=dot1x_port.number,
eth_type=valve_packet.ETH_EAPOL),
priority=self.acl_priority,
)
]
return ofmsgs
[docs] def create_acl_tunnel(self, dp):
"""
Create tunnel acls from ACLs that require applying in DP \
Returns flowmods for the tunnel
Args:
dp (DP): DP that contains the tunnel acls to build
"""
ofmsgs = []
if dp.tunnel_acls:
for tunnel_id, tunnel_acl in dp.tunnel_acls.items():
if not dp.tunnel_updated_flags[tunnel_id]:
continue
in_port_match = tunnel_acl.get_in_port_match(tunnel_id)
vlan_match = None
if in_port_match is None:
vlan_match = tunnel_id
vlan_table = dp.tables.get('vlan')
acl_table = self.vlan_acl_table
acl_allow_inst = None
acl_force_port_vlan_inst = None
#TODO: This will be handled by the vlan manager
# VLANs with reserved_internal_vlan=True will
# handle creating this flow for us
ofmsgs.append(vlan_table.flowmod(
match=vlan_table.match(vlan=tunnel_id),
priority=self.auth_priority,
inst=[vlan_table.goto(acl_table)]
))
else:
acl_table = self.port_acl_table
acl_allow_inst = self.pipeline.accept_to_vlan()
acl_force_port_vlan_inst = self.pipeline.accept_to_l2_forwarding()
ofmsgs.extend(build_acl_ofmsgs(
[tunnel_acl], acl_table, acl_allow_inst,
acl_force_port_vlan_inst, self.acl_priority,
self.meters, False, in_port_match, vlan_match
))
return ofmsgs