def resolve_vlan_names_in_routers(): """Resolve VLAN references in routers.""" dp_routers = {} for router_name, router in list(self.routers.items()): vlans = [] for vlan_name in router.vlans: vlan = resolve_vlan(vlan_name) if vlan is not None: vlans.append(vlan) if len(vlans) > 1: dp_router = copy.copy(router) dp_router.vlans = vlans dp_routers[router_name] = dp_router vips = set() for vlan in vlans: for vip in vlan.faucet_vips: if vip.ip.is_link_local: continue vips.add(vip) for vip in vips: for other_vip in vips - set([vip]): test_config_condition( vip.ip in other_vip.network, 'VIPs %s and %s overlap in router %s' % ( vip, other_vip, router_name)) self.routers = dp_routers
def resolve_acls(): """Resolve config references in ACLs.""" # TODO: move this config validation to ACL object. for vlan in list(self.vlans.values()): if vlan.acls_in: acls = [] for acl in vlan.acls_in: resolve_acl(acl, vlan.vid) acls.append(self.acls[acl]) vlan.acls_in = acls verify_acl_exact_match(acls) for port in list(self.ports.values()): if port.acls_in: test_config_condition(self.dp_acls, ( 'dataplane ACLs cannot be used with port ACLs.')) acls = [] for acl in port.acls_in: resolve_acl(acl, port_num=port.number) acls.append(self.acls[acl]) port.acls_in = acls verify_acl_exact_match(acls) if self.dp_acls: acls = [] for acl in self.acls: resolve_acl(acl, None) acls.append(self.acls[acl]) self.dp_acls = acls
def check_config(self): super(Router, self).check_config() if self.bgp: self._check_conf_types(self.bgp, self.bgp_defaults_types) self.bgp = self._set_unknown_conf(self.bgp, self.bgp_defaults_types) if not self.bgp_connect_mode(): self.bgp['bgp']['connect_mode'] = 'passive' for field in self.ipaddress_fields: if field in self.bgp: self.bgp[field] = frozenset([ self._check_ip_str(ip_str) for ip_str in self.bgp[field]]) for accessor_val, required_field in ( (self.bgp_ipvs(), 'server_addresses'), (self.bgp_as(), 'as'), (self.bgp_port(), 'port'), (self.bgp_connect_mode(), 'connect_mode'), (self.bgp_routerid(), 'routerid'), (self.bgp_neighbor_addresses(), 'neighbor_addresses'), (self.bgp_neighbor_as(), 'neighbor_as')): test_config_condition(not accessor_val, 'BGP %s must be specified' % required_field) test_config_condition( self.bgp_connect_mode() != 'passive', 'BGP connect_mode must be passive') for ipv in self.bgp_ipvs(): test_config_condition( len(self.bgp_server_addresses_by_ipv(ipv)) != 1, 'Only one BGP server address per IP version supported') if not self.bgp_vlan(): test_config_condition( len(self.vlans) != 1, 'If routing more than one VLAN, must specify BGP VLAN') self.set_bgp_vlan(self.vlans[0]) else: test_config_condition( not self.vlans, 'A router must have least one VLAN specified at top level')
def __init__(self, _id, dp_id, conf): self.rules = [] self.exact_match = None self.meter = False self.matches = {} self.set_fields = set() for match_fields in (MATCH_FIELDS, OLD_MATCH_FIELDS): self.rule_types.update({match: (str, int) for match in match_fields.keys()}) conf = copy.deepcopy(conf) if isinstance(conf, dict): rules = conf.get('rules', []) elif isinstance(conf, list): rules = conf conf = {} else: raise InvalidConfigError( 'ACL conf is an invalid type %s' % _id) conf['rules'] = [] for rule in rules: normalized_rule = rule if isinstance(rule, dict): normalized_rule = rule.get('rule', rule) if normalized_rule is None: normalized_rule = {k: v for k, v in rule.items() if v is not None} test_config_condition(not isinstance(normalized_rule, dict), ( 'ACL rule is %s not %s (%s)' % (type(normalized_rule), dict, rules))) conf['rules'].append(normalized_rule) super(ACL, self).__init__(_id, dp_id, conf)
def resolve_override_output_ports(): """Resolve override output ports.""" for port_no, port in list(self.ports.items()): if port.override_output_port: port.override_output_port = self.resolve_port(port.override_output_port) test_config_condition(not port.override_output_port, ( 'override_output_port port not defined')) self.ports[port_no] = port
def resolve_vlan(vlan_name): """Resolve VLAN by name or VID.""" test_config_condition(not isinstance(vlan_name, (str, int)), ( 'VLAN must be type %s or %s not %s' % (str, int, type(vlan_name)))) if vlan_name in vlan_by_name: return vlan_by_name[vlan_name] if vlan_name in self.vlans: return self.vlans[vlan_name] return None
def _map_port_num_to_port(ports_conf): port_num_to_port_conf = {} for port_key, port_conf in list(ports_conf.items()): test_config_condition(not isinstance(port_conf, dict), 'Invalid syntax in port config') port_num = port_conf.get('number', port_key) try: port_num_to_port_conf[port_num] = (port_key, port_conf) except TypeError: raise InvalidConfigError('Invalid syntax in port config') return port_num_to_port_conf
def _get_vlan_by_key(dp_id, vlan_key, vlans): test_config_condition(not isinstance(vlan_key, (str, int)), ( 'VLAN key must not be type %s' % type(vlan_key))) if vlan_key in vlans: return vlans[vlan_key] for vlan in list(vlans.values()): if vlan_key == str(vlan.vid): return vlan # Create VLAN with VID, if not defined. return vlans.setdefault(vlan_key, VLAN(vlan_key, dp_id))
def _dp_add_ports(dp, dp_conf, dp_id, vlans): ports_conf = dp_conf.get('interfaces', {}) port_ranges_conf = dp_conf.get('interface_ranges', {}) # as users can config port VLAN by using VLAN name, we store vid in # Port instance instead of VLAN name for data consistency test_config_condition(not isinstance(ports_conf, dict), ( 'Invalid syntax in interface config')) test_config_condition(not isinstance(port_ranges_conf, dict), ( 'Invalid syntax in interface ranges config')) def _map_port_num_to_port(ports_conf): port_num_to_port_conf = {} for port_key, port_conf in list(ports_conf.items()): test_config_condition(not isinstance(port_conf, dict), 'Invalid syntax in port config') port_num = port_conf.get('number', port_key) try: port_num_to_port_conf[port_num] = (port_key, port_conf) except TypeError: raise InvalidConfigError('Invalid syntax in port config') return port_num_to_port_conf def _parse_port_ranges(port_ranges_conf, port_num_to_port_conf): for port_range, port_conf in list(port_ranges_conf.items()): # port range format: 1-6 OR 1-6,8-9 OR 1-3,5,7-9 test_config_condition(not isinstance(port_conf, dict), 'Invalid syntax in port config') port_nums = set() if 'number' in port_conf: del port_conf['number'] for range_ in re.findall(r'(\d+-\d+)', str(port_range)): start_num, end_num = [int(num) for num in range_.split('-')] test_config_condition(start_num >= end_num, ( 'Incorrect port range (%d - %d)' % (start_num, end_num))) port_nums.update(list(range(start_num, end_num + 1))) port_range = re.sub(range_, '', port_range) other_nums = [int(p) for p in re.findall(r'\d+', str(port_range))] port_nums.update(other_nums) test_config_condition(not port_nums, 'interface-ranges contain invalid config') for port_num in port_nums: if port_num in port_num_to_port_conf: # port range config has lower priority than individual port config for attr, value in list(port_conf.items()): port_num_to_port_conf[port_num][1].setdefault(attr, value) else: port_num_to_port_conf[port_num] = (port_num, port_conf) port_num_to_port_conf = _map_port_num_to_port(ports_conf) _parse_port_ranges(port_ranges_conf, port_num_to_port_conf) for port_num, port_conf in list(port_num_to_port_conf.values()): port = _dp_parse_port(dp_id, port_num, port_conf, vlans) dp.add_port(port) dp.reset_refs(vlans=vlans)
def _parse_dp(dp_key, dp_conf, acls_conf, meters_conf, routers_conf, vlans_conf): test_config_condition(not isinstance(dp_conf, dict), '') dp = DP(dp_key, dp_conf.get('dp_id', None), dp_conf) test_config_condition(dp.name != dp_key, ( 'DP key %s and DP name must match' % dp_key)) dp_id = dp.dp_id vlans = {} vids = set() for vlan_key, vlan_conf in list(vlans_conf.items()): vlan = VLAN(vlan_key, dp_id, vlan_conf) test_config_condition(str(vlan_key) not in (str(vlan.vid), vlan.name), ( 'VLAN %s key must match VLAN name or VLAN VID' % vlan_key)) test_config_condition(vlan.vid in vids, ( 'VLAN VID %u multiply configured' % vlan.vid)) vlans[vlan_key] = vlan vids.add(vlan.vid) for acl_key, acl_conf in list(acls_conf.items()): acl = ACL(acl_key, dp_id, acl_conf) dp.add_acl(acl_key, acl) for router_key, router_conf in list(routers_conf.items()): router = Router(router_key, dp_id, router_conf) dp.add_router(router_key, router) for meter_key, meter_conf in list(meters_conf.items()): meter = Meter(meter_key, dp_id, meter_conf) dp.meters[meter_key] = meter _dp_add_ports(dp, dp_conf, dp_id, vlans) return dp
def resolve_stack_dps(): """Resolve DP references in stacking config.""" port_stack_dp = {} for port in self.stack_ports: stack_dp = port.stack['dp'] test_config_condition(stack_dp not in dp_by_name, ( 'stack DP %s not defined' % stack_dp)) port_stack_dp[port] = dp_by_name[stack_dp] for port, dp in list(port_stack_dp.items()): port.stack['dp'] = dp stack_port = dp.resolve_port(port.stack['port']) test_config_condition(stack_port is None, ( 'stack port %s not defined in DP %s' % (port.stack['port'], dp.name))) port.stack['port'] = stack_port
def _generate_acl_tables(self): all_acls = {} if self.dot1x: all_acls['port_acl'] = [PORT_ACL_8021X] for vlan in self.vlans.values(): if vlan.acls_in: all_acls.setdefault('vlan_acl', []) all_acls['vlan_acl'].extend(vlan.acls_in) if self.dp_acls: test_config_condition(self.dot1x, ( 'DP ACLs and 802.1x cannot be configured together')) for acl in self.dp_acls: all_acls['port_acl'] = self.dp_acls else: for port in self.ports.values(): if port.acls_in: test_config_condition(port.dot1x, ( 'port ACLs and 802.1x cannot be configured together')) all_acls.setdefault('port_acl', []) all_acls['port_acl'].extend(port.acls_in) table_config = {} for table_name, acls in all_acls.items(): matches = {} set_fields = set() meter = False exact_match = False default = faucet_pipeline.DEFAULT_CONFIGS[table_name] for acl in acls: for field, has_mask in acl.matches.items(): if has_mask or field not in matches: matches[field] = has_mask set_fields.update(acl.set_fields) meter = meter or acl.meter exact_match = acl.exact_match matches = set(matches.items()) table_config[table_name] = ValveTableConfig( table_name, default.table_id, exact_match=exact_match, meter=meter, output=True, match_types=matches, set_fields=tuple(set_fields), next_tables=default.next_tables) # TODO: dynamically configure output attribue return table_config
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 of_match, field in list(match_dict.items()): test_config_condition(of_match not in MATCH_FIELDS, 'Unknown match field: %s' % of_match) try: encoded_field = MATCH_FIELDS[of_match](field) except TypeError: raise InvalidConfigError('%s cannot be type %s' % (of_match, type(field))) kwargs[of_match] = encoded_field return parser.OFPMatch(**kwargs)
def build(self, meters, vid, port_num): """Check that ACL can be built from config.""" class NullRyuDatapath: """Placeholder Ryu Datapath.""" ofproto = valve_of.ofp self.matches = {} self.set_fields = set() self.meter = False if self.rules: try: ofmsgs = valve_acl.build_acl_ofmsgs( [self], wildcard_table, [valve_of.goto_table(wildcard_table)], [valve_of.goto_table(wildcard_table)], 2**16-1, meters, self.exact_match, vlan_vid=vid, port_num=port_num) except (netaddr.core.AddrFormatError, KeyError, ValueError) as err: raise InvalidConfigError(err) test_config_condition(not ofmsgs, 'OF messages is empty') for ofmsg in ofmsgs: ofmsg.datapath = NullRyuDatapath() ofmsg.set_xid(0) try: ofmsg.serialize() except (KeyError, ValueError) as err: raise InvalidConfigError(err) except Exception as err: print(ofmsg) raise err if valve_of.is_flowmod(ofmsg): apply_actions = [] for inst in ofmsg.instructions: if valve_of.is_apply_actions(inst): apply_actions.extend(inst.actions) elif valve_of.is_meter(inst): self.meter = True for action in apply_actions: if valve_of.is_set_field(action): self.set_fields.add(action.key) for match, value in ofmsg.match.items(): has_mask = isinstance(value, (tuple, list)) if has_mask or match not in self.matches: self.matches[match] = has_mask return (self.matches, self.set_fields, self.meter)
def _resolve_output_ports(self, action_conf, resolve_port_cb, resolve_tunnel_objects): result = {} for output_action, output_action_values in action_conf.items(): if output_action == 'tunnel': tunnel = output_action_values self._check_conf_types(tunnel, self.tunnel_types) src_dp, src_port, dst_dp, dst_port, tunnel_id = resolve_tunnel_objects( tunnel['dp'], tunnel['port'], tunnel['tunnel_id']) tunnel_dict = { 'src_dp': src_dp, 'src_port': src_port, 'dst_dp': dst_dp, 'dst_port': dst_port, 'tunnel_id': tunnel_id, 'type': tunnel['type'], } self.tunnel_info[tunnel_id] = tunnel_dict result[output_action] = tunnel_id elif output_action == 'port': port_name = output_action_values port = resolve_port_cb(port_name) test_config_condition( not port, ('ACL (%s) output port undefined in DP: %s'\ % (self._id, self.dp_id)) ) result[output_action] = port elif output_action == 'ports': resolved_ports = [ resolve_port_cb(p) for p in output_action_values] test_config_condition( None in resolved_ports, ('ACL (%s) output port(s) not defined in DP: %s'\ % (self._id, self.dp_id)) ) result[output_action] = resolved_ports elif output_action == 'failover': failover = output_action_values test_config_condition(not isinstance(failover, dict), ( 'failover is not a dictionary')) result[output_action] = {} for failover_name, failover_values in failover.items(): if failover_name == 'ports': resolved_ports = [ resolve_port_cb(p) for p in failover_values] test_config_condition( None in resolved_ports, ('ACL (%s) failover port(s) not defined in DP: %s'\ % (self._id, self.dp_id)) ) result[output_action][failover_name] = resolved_ports else: result[output_action][failover_name] = failover_values else: result[output_action] = output_action_values return result
def __init__(self, _id, dp_id, conf): self.rules = [] self.exact_match = None self.meter = False self.matches = {} self.set_fields = set() #TODO: Would be possible to save the names instead of the DP and port objects # TUNNEL: # src_port: PORT object / port number # source port # src_dp: DP object # source dp # dst_port: PORT object / port number # final destination port # dst_dp: DP object # final destination dp # tunnel_id: int # ID to represent the tunnel # tunnel_type: str ('vlan') # tunnel type specification self.tunnel_info = {} for match_fields in (MATCH_FIELDS, OLD_MATCH_FIELDS): self.rule_types.update({match: (str, int) for match in match_fields}) conf = copy.deepcopy(conf) if isinstance(conf, dict): rules = conf.get('rules', []) elif isinstance(conf, list): rules = conf conf = {} else: raise InvalidConfigError( 'ACL conf is an invalid type %s' % _id) conf['rules'] = [] for rule in rules: normalized_rule = rule if isinstance(rule, dict): normalized_rule = rule.get('rule', rule) if normalized_rule is None: normalized_rule = {k: v for k, v in rule.items() if v is not None} test_config_condition(not isinstance(normalized_rule, dict), ( 'ACL rule is %s not %s (%s)' % (type(normalized_rule), dict, rules))) conf['rules'].append(normalized_rule) super(ACL, self).__init__(_id, dp_id, conf)
def resolve_mirror_destinations(): """Resolve mirror port references and destinations.""" mirror_from_port = defaultdict(list) for mirror_port in list(self.ports.values()): if mirror_port.mirror is not None: mirrored_ports = resolve_ports(mirror_port.mirror) test_config_condition(len(mirrored_ports) != len(mirror_port.mirror), ( 'port mirror not defined in DP %s' % self.name)) for mirrored_port in mirrored_ports: mirror_from_port[mirrored_port].append(mirror_port) # TODO: confusingly, mirror at config time means what ports to mirror from. # But internally we use as a list of ports to mirror to. for mirrored_port, mirror_ports in list(mirror_from_port.items()): mirrored_port.mirror = [] for mirror_port in mirror_ports: mirrored_port.mirror.append(mirror_port.number) mirror_port.output_only = True
def _dp_parse_port(dp_id, port_key, port_conf, vlans): def _dp_parse_native_port_vlan(): if port.native_vlan is not None: vlan = _get_vlan_by_key(dp_id, port.native_vlan, vlans) port.native_vlan = vlan def _dp_parse_tagged_port_vlans(): if port.tagged_vlans: port_tagged_vlans = [ _get_vlan_by_key(dp_id, vlan_key, vlans) for vlan_key in port.tagged_vlans] port.tagged_vlans = port_tagged_vlans port = Port(port_key, dp_id, port_conf) test_config_condition(str(port_key) not in (str(port.number), port.name), ( 'Port key %s match port name or port number' % port_key)) _dp_parse_native_port_vlan() _dp_parse_tagged_port_vlans() return port
def _dp_parser_v2(acls_conf, dps_conf, meters_conf, routers_conf, vlans_conf): dps = [_parse_dp(dp_key, dp_conf, acls_conf, meters_conf, routers_conf, vlans_conf) for dp_key, dp_conf in list(dps_conf.items())] for dp in dps: dp.finalize_config(dps) for dp in dps: dp.resolve_stack_topology(dps) router_ref_dps = collections.defaultdict(set) for dp in dps: for router in list(dp.routers.keys()): router_ref_dps[router].add(dp) for router in list(routers_conf.keys()): test_config_condition(not router_ref_dps[router], ( 'router %s configured but not used by any DP' % router)) return dps
def resolve_acl(acl_in, vid=None, port_num=None): """Resolve an individual ACL.""" test_config_condition(acl_in not in self.acls, ( 'missing ACL %s in DP: %s' % (acl_in, self.name))) acl = self.acls[acl_in] def resolve_port_cb(port_name): port = self.resolve_port(port_name) if port: return port.number return port acl.resolve_ports(resolve_port_cb) for meter_name in acl.get_meters(): test_config_condition(meter_name not in self.meters, ( 'meter %s is not configured' % meter_name)) for port_no in acl.get_mirror_destinations(): port = self.ports[port_no] port.output_only = True return acl.build(self.meters, vid, port_num)
def __init__(self, _id, dp_id, conf): self.rules = [] self.exact_match = None self.meter = False self.matches = {} self.set_fields = set() conf = copy.deepcopy(conf) if isinstance(conf, dict): rules = conf.get('rules', []) elif isinstance(conf, list): rules = conf conf = {} else: raise InvalidConfigError( 'ACL conf is an invalid type %s' % self._id) conf['rules'] = [] for rule in rules: test_config_condition(not isinstance(rule, dict), ( 'ACL rule is %s not %s' % (type(rule), dict))) conf['rules'].append(rule.get('rule', rule)) super(ACL, self).__init__(_id, dp_id, conf)
def dp_parser(config_file, logname): """Parse a config file into DP configuration objects with hashes of config include/files.""" conf = config_parser_util.read_config(config_file, logname) config_hashes = None dps = None test_config_condition(conf is None, 'Config file is empty') test_config_condition(not isinstance(conf, dict), 'Config file does not have valid syntax') version = conf.pop('version', 2) test_config_condition(version != 2, 'Only config version 2 is supported') config_hashes, dps = _config_parser_v2(config_file, logname) test_config_condition(dps is None, 'no DPs are not defined') return config_hashes, dps
def resolve_ports(self, resolve_port_cb, resolve_tunnel_objects): for rule_conf in self.rules: if 'actions' in rule_conf: actions_conf = rule_conf['actions'] resolved_actions = {} test_config_condition(not isinstance(actions_conf, dict), ( 'actions value is not a dictionary')) for action_name, action_conf in actions_conf.items(): if action_name == 'mirror': resolved_port = resolve_port_cb(action_conf) test_config_condition( resolved_port is None, ('ACL (%s) mirror port is not defined in DP: %s'\ % (self._id, self.dp_id)) ) resolved_actions[action_name] = resolved_port elif action_name == 'output': resolved_action = self._resolve_output_ports( action_conf, resolve_port_cb, resolve_tunnel_objects) resolved_actions[action_name] = resolved_action else: resolved_actions[action_name] = action_conf rule_conf['actions'] = resolved_actions
def finalize(self): test_config_condition(not (self.vlans() or self.stack or self.output_only), ( '%s must have a VLAN, be a stack port, or have output_only: True' % self)) test_config_condition(self.vlans() and self.stack, ( '%s cannot have stack and VLANs on same port' % self)) if self.native_vlan: test_config_condition(self.native_vlan in self.tagged_vlans, ( 'cannot have same native and tagged VLAN on same port')) self.tagged_vlans = tuple(self.tagged_vlans) super(Port, self).finalize()
def _resolve_output_ports(self, action_conf, resolve_port_cb): result = {} for output_action, output_action_values in list(action_conf.items()): if output_action == 'port': port_name = output_action_values port = resolve_port_cb(port_name) test_config_condition( not port, ('ACL (%s) output port undefined in DP: %s'\ % (self._id, self.dp_id)) ) result[output_action] = port elif output_action == 'ports': resolved_ports = [ resolve_port_cb(p) for p in output_action_values] test_config_condition( None in resolved_ports, ('ACL (%s) output port(s) not defined in DP: %s'\ % (self._id, self.dp_id)) ) result[output_action] = resolved_ports elif output_action == 'failover': failover = output_action_values test_config_condition(not isinstance(failover, dict), ( 'failover is not a dictionary')) result[output_action] = {} for failover_name, failover_values in list(failover.items()): if failover_name == 'ports': resolved_ports = [ resolve_port_cb(p) for p in failover_values] test_config_condition( None in resolved_ports, ('ACL (%s) failover port(s) not defined in DP: %s'\ % (self._id, self.dp_id)) ) result[output_action][failover_name] = resolved_ports else: result[output_action][failover_name] = failover_values else: result[output_action] = output_action_values return result
def check_config(self): test_config_condition( not self.rules, 'no rules found for ACL %s' % self._id) for rule in self.rules: self._check_conf_types(rule, self.rule_types) for rule_field, rule_conf in rule.items(): if rule_field == 'cookie': test_config_condition( rule_conf < 0 or rule_conf > 2**16, 'rule cookie value must be 0-2**16') elif rule_field == 'actions': test_config_condition( not rule_conf, 'Missing rule actions in ACL %s' % self._id) self._check_conf_types(rule_conf, self.actions_types) for action_name, action_conf in rule_conf.items(): if action_name == 'output': self._check_conf_types( action_conf, self.output_actions_types)
def check_config(self): test_config_condition(not self.rules, 'no rules found for ACL %s' % self._id) for match_fields in (MATCH_FIELDS, OLD_MATCH_FIELDS): for match in list(match_fields.keys()): self.rule_types[match] = (str, int) for rule in self.rules: self._check_conf_types(rule, self.rule_types) for rule_field, rule_conf in list(rule.items()): if rule_field == 'cookie': test_config_condition( rule_conf < 0 or rule_conf > 2**16, ('rule cookie value must be 0-2**16')) elif rule_field == 'actions': test_config_condition( not rule_conf, 'Missing rule actions in ACL %s' % self._id) self._check_conf_types(rule_conf, self.actions_types) for action_name, action_conf in list(rule_conf.items()): if action_name == 'output': self._check_conf_types(action_conf, self.output_actions_types)
def check_config(self): test_config_condition( not self.rules, 'no rules found for ACL %s' % self._id) for match_fields in (MATCH_FIELDS, OLD_MATCH_FIELDS): for match in list(match_fields.keys()): self.rule_types[match] = (str, int) for rule in self.rules: self._check_conf_types(rule, self.rule_types) for rule_field, rule_conf in list(rule.items()): if rule_field == 'cookie': test_config_condition(rule_conf < 0 or rule_conf > 2**16, ( 'rule cookie value must be 0-2**16')) elif rule_field == 'actions': test_config_condition( not rule_conf, 'Missing rule actions in ACL %s' % self._id ) self._check_conf_types(rule_conf, self.actions_types) for action_name, action_conf in list(rule_conf.items()): if action_name == 'output': self._check_conf_types( action_conf, self.output_actions_types)
def _parse_port_ranges(port_ranges_conf, port_num_to_port_conf): for port_range, port_conf in list(port_ranges_conf.items()): # port range format: 1-6 OR 1-6,8-9 OR 1-3,5,7-9 test_config_condition(not isinstance(port_conf, dict), 'Invalid syntax in port config') port_nums = set() if 'number' in port_conf: del port_conf['number'] for range_ in re.findall(r'(\d+-\d+)', str(port_range)): start_num, end_num = [int(num) for num in range_.split('-')] test_config_condition(start_num >= end_num, ( 'Incorrect port range (%d - %d)' % (start_num, end_num))) port_nums.update(list(range(start_num, end_num + 1))) port_range = re.sub(range_, '', port_range) other_nums = [int(p) for p in re.findall(r'\d+', str(port_range))] port_nums.update(other_nums) test_config_condition(not port_nums, 'interface-ranges contain invalid config') for port_num in port_nums: if port_num in port_num_to_port_conf: # port range config has lower priority than individual port config for attr, value in list(port_conf.items()): port_num_to_port_conf[port_num][1].setdefault(attr, value) else: port_num_to_port_conf[port_num] = (port_num, port_conf)
def _parse_port_ranges(port_ranges_conf, port_num_to_port_conf): for port_range, port_conf in port_ranges_conf.items(): # port range format: 1-6 OR 1-6,8-9 OR 1-3,5,7-9 test_config_condition(not isinstance(port_conf, dict), 'Invalid syntax in port config') port_nums = set() if 'number' in port_conf: del port_conf['number'] for range_ in re.findall(r'(\d+-\d+)', str(port_range)): start_num, end_num = [int(num) for num in range_.split('-')] test_config_condition(start_num >= end_num, ( 'Incorrect port range (%d - %d)' % (start_num, end_num))) port_nums.update(range(start_num, end_num + 1)) port_range = re.sub(range_, '', port_range) other_nums = [int(p) for p in re.findall(r'\d+', str(port_range))] port_nums.update(other_nums) test_config_condition(not port_nums, 'interface-ranges contain invalid config') for port_num in port_nums: if port_num in port_num_to_port_conf: # port range config has lower priority than individual port config for attr, value in port_conf.items(): port_num_to_port_conf[port_num][1].setdefault(attr, value) else: port_num_to_port_conf[port_num] = (port_num, port_conf)
def check_config(self): super(VLAN, self).check_config() test_config_condition(not self.vid_valid(self.vid), 'invalid VID %s' % self.vid) test_config_condition(not netaddr.valid_mac(self.faucet_mac), ('invalid MAC address %s' % self.faucet_mac)) test_config_condition( self.acl_in and self.acls_in, 'found both acl_in and acls_in, use only acls_in') if self.acl_in and not isinstance(self.acl_in, list): self.acls_in = [ self.acl_in, ] self.acl_in = None if self.acls_in: for acl in self.acls_in: test_config_condition(not isinstance(acl, (int, str)), 'acl names must be int or str') if self.max_hosts: if not self.proactive_arp_limit: self.proactive_arp_limit = 2 * self.max_hosts if not self.proactive_nd_limit: self.proactive_nd_limit = 2 * self.max_hosts if self.faucet_vips: self.faucet_vips = frozenset([ self._check_ip_str(ip_str, ip_method=ipaddress.ip_interface) for ip_str in self.faucet_vips ]) if self.bgp_neighbor_addresses or self.bgp_neighbour_addresses: neigh_addresses = frozenset(self.bgp_neighbor_addresses + self.bgp_neighbour_addresses) self.bgp_neighbor_addresses = frozenset( [self._check_ip_str(ip_str) for ip_str in neigh_addresses]) if self.bgp_server_addresses: self.bgp_server_addresses = frozenset([ self._check_ip_str(ip_str) for ip_str in self.bgp_server_addresses ]) for ipv in self.bgp_ipvs(): test_config_condition( len(self.bgp_server_addresses_by_ipv(ipv)) != 1, 'Only one BGP server address per IP version supported') if self.bgp_as: test_config_condition(not isinstance(self.bgp_port, int), ('BGP port must be %s not %s' % (int, type(self.bgp_port)))) test_config_condition(self.bgp_connect_mode not in ('passive'), ('BGP connect mode %s must be passive' % self.bgp_connect_mode)) test_config_condition( not ipaddress.IPv4Address(btos(self.bgp_routerid)), ('%s is not a valid IPv4 address' % (self.bgp_routerid))) test_config_condition(not self.bgp_neighbor_as, 'No BGP neighbor AS') test_config_condition(not self.bgp_neighbor_addresses, 'No BGP neighbor addresses') test_config_condition( len(self.bgp_neighbor_addresses) != len( self.bgp_neighbor_addresses), ('Must be as many BGP neighbor addresses as BGP server addresses' )) if self.routes: test_config_condition(not isinstance(self.routes, list), 'invalid VLAN routes format') try: self.routes = [route['route'] for route in self.routes] except TypeError: raise InvalidConfigError('%s is not a valid routes value' % self.routes) except KeyError: pass for route in self.routes: test_config_condition(not isinstance(route, dict), 'invalid VLAN route format') test_config_condition('ip_gw' not in route, 'missing ip_gw in VLAN route') test_config_condition('ip_dst' not in route, 'missing ip_dst in VLAN route') ip_gw = self._check_ip_str(route['ip_gw']) ip_dst = self._check_ip_str(route['ip_dst'], ip_method=ipaddress.ip_network) test_config_condition( ip_gw.version != ip_dst.version, 'ip_gw version does not match the ip_dst version') self.add_route(ip_dst, ip_gw)
def check_config(self): super(VLAN, self).check_config() test_config_condition(not self.vid_valid(self.vid), 'invalid VID %s' % self.vid) test_config_condition(not netaddr.valid_mac(self.faucet_mac), ('invalid MAC address %s' % self.faucet_mac)) test_config_condition( self.acl_in and self.acls_in, 'found both acl_in and acls_in, use only acls_in') test_config_condition( self.acl_out and self.acls_out, 'found both acl_out and acls_out, use only acls_out') if self.acl_in and not isinstance(self.acl_in, list): self.acls_in = [ self.acl_in, ] self.acl_in = None if self.acl_out and not isinstance(self.acl_out, list): self.acls_out = [ self.acl_out, ] self.acl_out = None all_acls = [] if self.acls_in: all_acls.extend(self.acls_in) if self.acls_out: all_acls.extend(self.acls_out) for acl in all_acls: test_config_condition(not isinstance(acl, (int, str)), 'acl names must be int or str') if self.max_hosts: if not self.proactive_arp_limit: self.proactive_arp_limit = 2 * self.max_hosts if not self.proactive_nd_limit: self.proactive_nd_limit = 2 * self.max_hosts if self.faucet_vips: self.faucet_vips = frozenset([ self._check_ip_str(ip_str, ip_method=ipaddress.ip_interface) for ip_str in self.faucet_vips ]) if self.routes: test_config_condition(not isinstance(self.routes, list), 'invalid VLAN routes format') try: self.routes = [route['route'] for route in self.routes] except TypeError: raise InvalidConfigError('%s is not a valid routes value' % self.routes) except KeyError: pass for route in self.routes: test_config_condition(not isinstance(route, dict), 'invalid VLAN route format') test_config_condition('ip_gw' not in route, 'missing ip_gw in VLAN route') test_config_condition('ip_dst' not in route, 'missing ip_dst in VLAN route') ip_gw = self._check_ip_str(route['ip_gw']) ip_dst = self._check_ip_str(route['ip_dst'], ip_method=ipaddress.ip_network) test_config_condition( ip_gw.version != ip_dst.version, 'ip_gw version does not match the ip_dst version') self.add_route(ip_dst, ip_gw)
def check_config(self): super().check_config() test_config_condition(self.meter_id < 0, 'meter_id is than 0') test_config_condition( self.meter_id > 4294901760, 'DP meter_id cannot exceed 4294901760 per OF13 specification')
def resolve_meter(_acl, action_conf): meter_name = action_conf test_config_condition( meter_name not in self.meters, ('meter %s is not configured' % meter_name)) return action_conf
def _resolve_ordered_output_ports(self, output_list, resolve_port_cb, resolve_tunnel_objects): """Resolve output actions in the ordered list format""" result = [] for action in output_list: for key, value in action.items(): if key == 'tunnel': tunnel = value # Fetch tunnel items from the tunnel output dict test_config_condition( 'dp' not in tunnel, 'ACL (%s) tunnel DP not defined' % self._id) tunnel_dp = tunnel['dp'] tunnel_port = tunnel.get('port', None) tunnel_id = tunnel.get('tunnel_id', None) tunnel_type = tunnel.get('type', 'vlan') tunnel_exit_instructions = tunnel.get('exit_instructions', []) tunnel_direction = tunnel.get('bi_directional', False) tunnel_maintain = tunnel.get('maintain_encapsulation', False) tunnel_reverse = tunnel.get('reverse', False) test_config_condition( tunnel_reverse and tunnel_direction, ('Tunnel ACL %s cannot contain values for the fields' '`bi_directional` and `reverse` at the same time' % self._id)) # Resolve the tunnel items dst_dp, dst_port, tunnel_id = resolve_tunnel_objects( tunnel_dp, tunnel_port, tunnel_id) # Compile the tunnel into an easy-access dictionary tunnel_dict = { 'dst_dp': dst_dp, 'dst_port': dst_port, 'tunnel_id': tunnel_id, 'type': tunnel_type, 'exit_instructions': tunnel_exit_instructions, 'bi_directional': tunnel_direction, 'maintain_encapsulation': tunnel_maintain, 'reverse': tunnel_reverse, } self.tunnel_dests[tunnel_id] = tunnel_dict result.append({key: tunnel_id}) elif key == 'port': port_name = value port = resolve_port_cb(port_name) test_config_condition( not port, 'ACL (%s) output port undefined in DP: %s' % (self._id, self.dp_id)) result.append({key: port}) elif key == 'ports': resolved_ports = [ resolve_port_cb(p) for p in value] test_config_condition( None in resolved_ports, 'ACL (%s) output port(s) not defined in DP: %s' % (self._id, self.dp_id)) result.append({key: resolved_ports}) elif key == 'failover': failover = value test_config_condition(not isinstance(failover, dict), ( 'failover is not a dictionary')) failover_dict = {} for failover_name, failover_values in failover.items(): if failover_name == 'ports': resolved_ports = [ resolve_port_cb(p) for p in failover_values] test_config_condition( None in resolved_ports, 'ACL (%s) failover port(s) not defined in DP: %s' % ( self._id, self.dp_id)) failover_dict[failover_name] = resolved_ports else: failover_dict[failover_name] = failover_values result.append({key: failover_dict}) else: result.append(action) return result
def _resolve_output_ports(self, action_conf, resolve_port_cb, resolve_tunnel_objects): """Resolve the values for output actions in the ACL""" if isinstance(action_conf, (list, tuple)): return self._resolve_ordered_output_ports( action_conf, resolve_port_cb, resolve_tunnel_objects) result = {} test_config_condition( 'vlan_vid' in action_conf and 'vlan_vids' in action_conf, 'ACL %s has both vlan_vid and vlan_vids defined' % self._id) test_config_condition( 'port' in action_conf and 'ports' in action_conf, 'ACL %s has both port and ports defined' % self._id) for output_action, output_action_values in action_conf.items(): if output_action == 'tunnel': tunnel = output_action_values # Fetch tunnel items from the tunnel output dict test_config_condition( 'dp' not in tunnel, 'ACL (%s) tunnel DP not defined' % self._id) tunnel_dp = tunnel['dp'] test_config_condition( 'port' not in tunnel, 'ACL (%s) tunnel port not defined' % self._id) tunnel_port = tunnel['port'] tunnel_id = tunnel.get('tunnel_id', None) tunnel_type = tunnel.get('type', 'vlan') # Resolve the tunnel items dst_dp, dst_port, tunnel_id = resolve_tunnel_objects( tunnel_dp, tunnel_port, tunnel_id) # Compile the tunnel into an easy-access dictionary tunnel_dict = { 'dst_dp': dst_dp, 'dst_port': dst_port, 'tunnel_id': tunnel_id, 'type': tunnel_type } self.tunnel_info[tunnel_id] = tunnel_dict result[output_action] = tunnel_id elif output_action == 'port': port_name = output_action_values port = resolve_port_cb(port_name) test_config_condition( not port, ('ACL (%s) output port undefined in DP: %s'\ % (self._id, self.dp_id)) ) result[output_action] = port elif output_action == 'ports': resolved_ports = [ resolve_port_cb(p) for p in output_action_values] test_config_condition( None in resolved_ports, ('ACL (%s) output port(s) not defined in DP: %s'\ % (self._id, self.dp_id)) ) result[output_action] = resolved_ports elif output_action == 'failover': failover = output_action_values test_config_condition(not isinstance(failover, dict), ( 'failover is not a dictionary')) result[output_action] = {} for failover_name, failover_values in failover.items(): if failover_name == 'ports': resolved_ports = [ resolve_port_cb(p) for p in failover_values] test_config_condition( None in resolved_ports, ('ACL (%s) failover port(s) not defined in DP: %s'\ % (self._id, self.dp_id)) ) result[output_action][failover_name] = resolved_ports else: result[output_action][failover_name] = failover_values else: result[output_action] = output_action_values return result
def check_config(self): super(VLAN, self).check_config() test_config_condition(not self.vid_valid(self.vid), 'invalid VID %s' % self.vid) test_config_condition(not netaddr.valid_mac(self.faucet_mac), ( 'invalid MAC address %s' % self.faucet_mac)) test_config_condition( self.acl_in and self.acls_in, 'found both acl_in and acls_in, use only acls_in') if self.acl_in and not isinstance(self.acl_in, list): self.acls_in = [self.acl_in,] self.acl_in = None if self.acls_in: for acl in self.acls_in: test_config_condition( not isinstance(acl, (int, str)), 'acl names must be int or str') if self.max_hosts: if not self.proactive_arp_limit: self.proactive_arp_limit = 2 * self.max_hosts if not self.proactive_nd_limit: self.proactive_nd_limit = 2 * self.max_hosts if self.faucet_vips: self.faucet_vips = frozenset([ self._check_ip_str(ip_str, ip_method=ipaddress.ip_interface) for ip_str in self.faucet_vips]) if self.routes: test_config_condition(not isinstance(self.routes, list), 'invalid VLAN routes format') try: self.routes = [route['route'] for route in self.routes] except TypeError: raise InvalidConfigError('%s is not a valid routes value' % self.routes) except KeyError: pass for route in self.routes: test_config_condition(not isinstance(route, dict), 'invalid VLAN route format') test_config_condition('ip_gw' not in route, 'missing ip_gw in VLAN route') test_config_condition('ip_dst' not in route, 'missing ip_dst in VLAN route') ip_gw = self._check_ip_str(route['ip_gw']) ip_dst = self._check_ip_str(route['ip_dst'], ip_method=ipaddress.ip_network) test_config_condition( ip_gw.version != ip_dst.version, 'ip_gw version does not match the ip_dst version') self.add_route(ip_dst, ip_gw)
def finalize_config(self, dps): """Perform consistency checks after initial config parsing.""" dp_by_name = {} vlan_by_name = {} def resolve_ports(port_names): """Resolve list of ports, by port by name or number.""" resolved_ports = [] for port_name in port_names: port = self.resolve_port(port_name) if port is not None: resolved_ports.append(port) return resolved_ports def resolve_vlan(vlan_name): """Resolve VLAN by name or VID.""" test_config_condition(not isinstance(vlan_name, (str, int)), ('VLAN must be type %s or %s not %s' % (str, int, type(vlan_name)))) if vlan_name in vlan_by_name: return vlan_by_name[vlan_name] if vlan_name in self.vlans: return self.vlans[vlan_name] return None def resolve_stack_dps(): """Resolve DP references in stacking config.""" port_stack_dp = {} for port in self.stack_ports: stack_dp = port.stack['dp'] test_config_condition(stack_dp not in dp_by_name, ('stack DP %s not defined' % stack_dp)) port_stack_dp[port] = dp_by_name[stack_dp] for port, dp in list(port_stack_dp.items()): port.stack['dp'] = dp stack_port = dp.resolve_port(port.stack['port']) test_config_condition(stack_port is None, ('stack port %s not defined in DP %s' % (port.stack['port'], dp.name))) port.stack['port'] = stack_port def resolve_mirror_destinations(): """Resolve mirror port references and destinations.""" mirror_from_port = defaultdict(list) for mirror_port in list(self.ports.values()): if mirror_port.mirror is not None: mirrored_ports = resolve_ports(mirror_port.mirror) test_config_condition( len(mirrored_ports) != len(mirror_port.mirror), ('port mirror not defined in DP %s' % self.name)) for mirrored_port in mirrored_ports: mirror_from_port[mirrored_port].append(mirror_port) # TODO: confusingly, mirror at config time means what ports to mirror from. # But internally we use as a list of ports to mirror to. for mirrored_port, mirror_ports in list(mirror_from_port.items()): mirrored_port.mirror = [] for mirror_port in mirror_ports: mirrored_port.mirror.append(mirror_port.number) mirror_port.output_only = True def resolve_override_output_ports(): """Resolve override output ports.""" for port_no, port in list(self.ports.items()): if port.override_output_port: port.override_output_port = self.resolve_port( port.override_output_port) test_config_condition( not port.override_output_port, ('override_output_port port not defined')) self.ports[port_no] = port def resolve_acl(acl_in, vid): """Resolve an individual ACL.""" test_config_condition(acl_in not in self.acls, ('missing ACL %s in DP: %s' % (acl_in, self.name))) acl = self.acls[acl_in] def resolve_port_cb(port_name): port = self.resolve_port(port_name) if port: return port.number return port acl.resolve_ports(resolve_port_cb) for meter_name in acl.get_meters(): test_config_condition( meter_name not in self.meters, ('meter %s is not configured' % meter_name)) for port_no in acl.get_mirror_destinations(): port = self.ports[port_no] port.output_only = True return acl.build(vid, self.meters) def verify_acl_exact_match(acls): for acl in acls: test_config_condition( acl.exact_match != acls[0].exact_match, ('ACLs when used together must have consistent exact_match' )) return acls[0].exact_match def resolve_acls(valve_cl): """Resolve config references in ACLs.""" # TODO: move this config validation to ACL object. port_acl_enabled = valve_cl.STATIC_TABLE_IDS port_acl_matches = {} port_acl_set_fields = set() port_acl_exact_match = False port_acl_meter = False vlan_acl_matches = {} vlan_acl_exact_match = False vlan_acl_set_fields = set() vlan_acl_meter = False def merge_matches(matches, new_matches): for field, has_mask in list(new_matches.items()): if has_mask or field not in matches: matches[field] = has_mask for vlan in list(self.vlans.values()): if vlan.acls_in: acls = [] for acl in vlan.acls_in: matches, set_fields, meter = resolve_acl(acl, vlan.vid) merge_matches(vlan_acl_matches, matches) vlan_acl_set_fields = vlan_acl_set_fields.union( set_fields) if meter: vlan_acl_meter = True acls.append(self.acls[acl]) vlan.acls_in = acls vlan_acl_exact_match = verify_acl_exact_match(acls) for port in list(self.ports.values()): if port.acls_in: test_config_condition( self.dp_acls, ('dataplane ACLs cannot be used with port ACLs.')) acls = [] for acl in port.acls_in: matches, set_fields, meter = resolve_acl(acl, None) merge_matches(port_acl_matches, matches) port_acl_set_fields = port_acl_set_fields.union( set_fields) if meter: port_acl_meter = True acls.append(self.acls[acl]) port.acls_in = acls port_acl_exact_match = verify_acl_exact_match(acls) port_acl_enabled = True if self.dp_acls: acls = [] for acl in self.acls: matches, set_fields, meter = resolve_acl(acl, None) merge_matches(port_acl_matches, matches) port_acl_set_fields = port_acl_set_fields.union(set_fields) if meter: port_acl_meter = True acls.append(self.acls[acl]) self.dp_acls = acls port_acl_enabled = True if port_acl_enabled: port_acl_matches.update({'in_port': False}) port_acl_matches = { (field, mask) for field, mask in list(port_acl_matches.items()) } vlan_acl_matches = { (field, mask) for field, mask in list(vlan_acl_matches.items()) } # TODO: skip port_acl table if not configured. # TODO: dynamically configure output attribue override_table_config = { 'port_acl': ValveTableConfig('port_acl', exact_match=port_acl_exact_match, meter=port_acl_meter, output=True, match_types=port_acl_matches, set_fields=tuple(port_acl_set_fields)), 'vlan_acl': ValveTableConfig('vlan_acl', exact_match=vlan_acl_exact_match, meter=vlan_acl_meter, output=True, match_types=vlan_acl_matches, set_fields=tuple(vlan_acl_set_fields)), } return override_table_config def resolve_vlan_names_in_routers(): """Resolve VLAN references in routers.""" dp_routers = {} for router_name, router in list(self.routers.items()): vlans = [] for vlan_name in router.vlans: vlan = resolve_vlan(vlan_name) if vlan is not None: vlans.append(vlan) if len(vlans) > 1: dp_router = copy.copy(router) dp_router.vlans = vlans dp_routers[router_name] = dp_router vips = set() for vlan in vlans: for vip in vlan.faucet_vips: if vip.ip.is_link_local: continue vips.add(vip) for vip in vips: for other_vip in vips - set([vip]): test_config_condition( vip.ip in other_vip.network, 'VIPs %s and %s overlap in router %s' % (vip, other_vip, router_name)) self.routers = dp_routers test_config_condition( not self.vlans, 'no VLANs referenced by interfaces in %s' % self.name) valve_cl = SUPPORTED_HARDWARE.get(self.hardware, None) test_config_condition( not valve_cl, 'hardware %s must be in %s' % (self.hardware, list(SUPPORTED_HARDWARE.keys()))) for dp in dps: dp_by_name[dp.name] = dp for vlan in list(self.vlans.values()): vlan_by_name[vlan.name] = vlan if self.global_vlan: test_config_condition( self.global_vlan == vlan.vid, 'VLAN %u is reserved by global_vlan' % vlan.vid) resolve_stack_dps() resolve_mirror_destinations() resolve_override_output_ports() resolve_vlan_names_in_routers() override_table_config = resolve_acls(valve_cl) # Only configure IP routing tables if enabled. ipvs = set() for vlan in list(self.vlans.values()): ipvs = ipvs.union(vlan.ipvs()) for ipv in (4, 6): if ipv not in ipvs: table_name = 'ipv%u_fib' % ipv override_table_config[table_name] = ValveTableConfig( table_name) if not ipvs: override_table_config['vip'] = ValveTableConfig('vip') vlan_port_factor = len(self.vlans) * len(self.ports) self._configure_tables(override_table_config, valve_cl, vlan_port_factor) bgp_vlans = self.bgp_vlans() if bgp_vlans: for vlan in bgp_vlans: vlan_dps = [dp for dp in dps if vlan.vid in dp.vlans] test_config_condition( len(vlan_dps) != 1, ('DPs %s sharing a BGP speaker VLAN is unsupported')) router_ids = {vlan.bgp_routerid for vlan in bgp_vlans} test_config_condition( len(router_ids) != 1, 'BGP router IDs must all be the same') bgp_ports = {vlan.bgp_port for vlan in bgp_vlans} test_config_condition( len(bgp_ports) != 1, 'BGP ports must all be the same') for vlan in bgp_vlans: test_config_condition( vlan.bgp_server_addresses != (bgp_vlans[0].bgp_server_addresses), ('BGP server addresses must all be the same')) for port in list(self.ports.values()): port.finalize() for vlan in list(self.vlans.values()): vlan.finalize() for acl in list(self.acls.values()): acl.finalize() for router in list(self.routers.values()): router.finalize() self.finalize()
def __init__(self, _id, dp_id, conf): super(ACL, self).__init__(_id, dp_id, conf) rules = conf if isinstance(conf, dict): if 'exact_match' in conf and conf['exact_match']: self.exact_match = True test_config_condition('rules' not in conf, 'no rules found for ACL %s' % _id) rules = conf['rules'] self.rules = [] test_config_condition(not isinstance(rules, list), ( "ACL rules is %s not %s" % (type(rules), dict))) for match_fields in (MATCH_FIELDS, OLD_MATCH_FIELDS): for match in list(match_fields.keys()): self.rule_types[match] = (str, int) for rule in rules: test_config_condition(not isinstance(rule, dict), ( "ACL rule is %s not %s" % (type(rule), dict))) for rule_key, rule_content in list(rule.items()): test_config_condition(rule_key != 'rule', "Incorrect ACL rule key") test_config_condition(not isinstance(rule_content, dict), ( "ACL rule content is %s not %s") % (type(rule_content), dict)) self._check_conf_types(rule_content, self.rule_types) for rule_field, rule_conf in list(rule_content.items()): if rule_field == 'cookie': test_config_condition(rule_conf < 0 or rule_conf > 2**16, ( 'rule cookie value must be 0-2**16')) elif rule_field == 'actions': test_config_condition(not rule_conf, "Missing rule actions in ACL %s" % _id) self._check_conf_types(rule_conf, self.actions_types) for action_name, action_conf in list(rule_conf.items()): if action_name == 'output': self._check_conf_types(action_conf, self.output_actions_types) self.rules.append(rule_content)
def finalize_config(self, dps): """Perform consistency checks after initial config parsing.""" dp_by_name = {} vlan_by_name = {} def resolve_ports(port_names): """Resolve list of ports, by port by name or number.""" resolved_ports = [] for port_name in port_names: port = self.resolve_port(port_name) if port is not None: resolved_ports.append(port) return resolved_ports def resolve_vlan(vlan_name): """Resolve VLAN by name or VID.""" test_config_condition(not isinstance(vlan_name, (str, int)), ('VLAN must be type %s or %s not %s' % (str, int, type(vlan_name)))) if vlan_name in vlan_by_name: return vlan_by_name[vlan_name] if vlan_name in self.vlans: return self.vlans[vlan_name] return None def resolve_stack_dps(): """Resolve DP references in stacking config.""" port_stack_dp = {} for port in self.stack_ports: stack_dp = port.stack['dp'] test_config_condition(stack_dp not in dp_by_name, ('stack DP %s not defined' % stack_dp)) port_stack_dp[port] = dp_by_name[stack_dp] for port, dp in port_stack_dp.items(): port.stack['dp'] = dp stack_port = dp.resolve_port(port.stack['port']) test_config_condition(stack_port is None, ('stack port %s not defined in DP %s' % (port.stack['port'], dp.name))) port.stack['port'] = stack_port def resolve_mirror_destinations(): """Resolve mirror port references and destinations.""" mirror_from_port = defaultdict(list) for mirror_port in self.ports.values(): if mirror_port.mirror is not None: mirrored_ports = resolve_ports(mirror_port.mirror) test_config_condition( len(mirrored_ports) != len(mirror_port.mirror), ('port mirror not defined in DP %s' % self.name)) for mirrored_port in mirrored_ports: mirror_from_port[mirrored_port].append(mirror_port) # TODO: confusingly, mirror at config time means what ports to mirror from. # But internally we use as a list of ports to mirror to. for mirrored_port, mirror_ports in mirror_from_port.items(): mirrored_port.mirror = [] for mirror_port in mirror_ports: mirrored_port.mirror.append(mirror_port.number) mirror_port.output_only = True def resolve_override_output_ports(): """Resolve override output ports.""" for port_no, port in self.ports.items(): if port.override_output_port: port.override_output_port = self.resolve_port( port.override_output_port) test_config_condition( not port.override_output_port, ('override_output_port port not defined')) self.ports[port_no] = port def resolve_acl(acl_in, vid=None, port_num=None): """Resolve an individual ACL.""" test_config_condition(acl_in not in self.acls, ('missing ACL %s in DP: %s' % (acl_in, self.name))) acl = self.acls[acl_in] def resolve_port_cb(port_name): port = self.resolve_port(port_name) if port: return port.number return port acl.resolve_ports(resolve_port_cb) for meter_name in acl.get_meters(): test_config_condition( meter_name not in self.meters, ('meter %s is not configured' % meter_name)) for port_no in acl.get_mirror_destinations(): port = self.ports[port_no] port.output_only = True return acl.build(self.meters, vid, port_num) def verify_acl_exact_match(acls): for acl in acls: test_config_condition( acl.exact_match != acls[0].exact_match, ('ACLs when used together must have consistent exact_match' )) def resolve_acls(): """Resolve config references in ACLs.""" # TODO: move this config validation to ACL object. for vlan in self.vlans.values(): if vlan.acls_in: acls = [] for acl in vlan.acls_in: resolve_acl(acl, vlan.vid) acls.append(self.acls[acl]) vlan.acls_in = acls verify_acl_exact_match(acls) for port in self.ports.values(): if port.acls_in: test_config_condition( self.dp_acls, ('dataplane ACLs cannot be used with port ACLs.')) acls = [] for acl in port.acls_in: resolve_acl(acl, port_num=port.number) acls.append(self.acls[acl]) port.acls_in = acls verify_acl_exact_match(acls) if self.dp_acls: acls = [] for acl in self.acls: resolve_acl(acl, None) acls.append(self.acls[acl]) self.dp_acls = acls def resolve_vlan_names_in_routers(): """Resolve VLAN references in routers.""" dp_routers = {} for router_name, router in self.routers.items(): vlans = [] for vlan_name in router.vlans: vlan = resolve_vlan(vlan_name) if vlan is not None: vlans.append(vlan) if len(vlans) > 1: dp_router = copy.copy(router) dp_router.vlans = vlans dp_routers[router_name] = dp_router vips = set() for vlan in vlans: for vip in vlan.faucet_vips: if vip.ip.is_link_local: continue vips.add(vip) for vip in vips: for other_vip in vips - set([vip]): test_config_condition( vip.ip in other_vip.network, 'VIPs %s and %s overlap in router %s' % (vip, other_vip, router_name)) self.routers = dp_routers test_config_condition( not self.vlans, 'no VLANs referenced by interfaces in %s' % self.name) valve_cl = SUPPORTED_HARDWARE.get(self.hardware, None) test_config_condition( not valve_cl, 'hardware %s must be in %s' % (self.hardware, SUPPORTED_HARDWARE.keys())) for dp in dps: dp_by_name[dp.name] = dp for vlan in self.vlans.values(): vlan_by_name[vlan.name] = vlan if self.global_vlan: test_config_condition( self.global_vlan == vlan.vid, 'VLAN %u is reserved by global_vlan' % vlan.vid) resolve_stack_dps() resolve_mirror_destinations() resolve_override_output_ports() resolve_vlan_names_in_routers() resolve_acls() self._configure_tables(valve_cl) bgp_vlans = self.bgp_vlans() if bgp_vlans: for vlan in bgp_vlans: vlan_dps = [dp for dp in dps if vlan.vid in dp.vlans] test_config_condition( len(vlan_dps) != 1, ('DPs %s sharing a BGP speaker VLAN is unsupported')) router_ids = {vlan.bgp_routerid for vlan in bgp_vlans} test_config_condition( len(router_ids) != 1, 'BGP router IDs must all be the same') bgp_ports = {vlan.bgp_port for vlan in bgp_vlans} test_config_condition( len(bgp_ports) != 1, 'BGP ports must all be the same') for vlan in bgp_vlans: test_config_condition( vlan.bgp_server_addresses != (bgp_vlans[0].bgp_server_addresses), ('BGP server addresses must all be the same')) for port in self.ports.values(): port.finalize() for vlan in self.vlans.values(): vlan.finalize() for acl in self.acls.values(): acl.finalize() for router in self.routers.values(): router.finalize() self.finalize()
def resolve_stack_topology(self, dps): """Resolve inter-DP config for stacking.""" def canonical_edge(dp, port): peer_dp = port.stack['dp'] peer_port = port.stack['port'] sort_edge_a = (dp.name, port.name, dp, port) sort_edge_z = (peer_dp.name, peer_port.name, peer_dp, peer_port) sorted_edge = sorted((sort_edge_a, sort_edge_z)) edge_a, edge_b = sorted_edge[0][2:], sorted_edge[1][2:] return edge_a, edge_b def make_edge_name(edge_a, edge_z): edge_a_dp, edge_a_port = edge_a edge_z_dp, edge_z_port = edge_z return '%s:%s-%s:%s' % (edge_a_dp.name, edge_a_port.name, edge_z_dp.name, edge_z_port.name) def make_edge_attr(edge_a, edge_z): edge_a_dp, edge_a_port = edge_a edge_z_dp, edge_z_port = edge_z return { 'dp_a': edge_a_dp, 'port_a': edge_a_port, 'dp_z': edge_z_dp, 'port_z': edge_z_port } root_dp = None stack_dps = [] for dp in dps: if dp.stack is not None: stack_dps.append(dp) if 'priority' in dp.stack: test_config_condition(dp.stack['priority'] <= 0, ('stack priority must be > 0')) test_config_condition(root_dp is not None, 'cannot have multiple stack roots') root_dp = dp for vlan in list(dp.vlans.values()): test_config_condition( vlan.faucet_vips != [], ('routing + stacking not supported')) if root_dp is None: test_config_condition(stack_dps, 'stacking enabled but no root_dp') return edge_count = {} graph = networkx.MultiGraph() for dp in dps: if dp.stack_ports: graph.add_node(dp.name) for port in dp.stack_ports: edge = canonical_edge(dp, port) edge_a, edge_z = edge edge_name = make_edge_name(edge_a, edge_z) edge_attr = make_edge_attr(edge_a, edge_z) edge_a_dp, _ = edge_a edge_z_dp, _ = edge_z if edge_name not in edge_count: edge_count[edge_name] = 0 edge_count[edge_name] += 1 graph.add_edge(edge_a_dp.name, edge_z_dp.name, key=edge_name, port_map=edge_attr) if graph.size(): for edge_name, count in list(edge_count.items()): test_config_condition( count != 2, '%s defined only in one direction' % edge_name) if self.name in graph: if self.stack is None: self.stack = {} self.stack['root_dp'] = root_dp self.stack['graph'] = graph
def finalize_config(self, dps): """Perform consistency checks after initial config parsing.""" dp_by_name = {} vlan_by_name = {} def resolve_ports(port_names): """Resolve list of ports, by port by name or number.""" resolved_ports = [] for port_name in port_names: port = self.resolve_port(port_name) if port is not None: resolved_ports.append(port) return resolved_ports def resolve_port_numbers(port_names): """Resolve list of ports to numbers, by port by name or number.""" return [port.number for port in resolve_ports(port_names)] def resolve_vlan(vlan_name): """Resolve VLAN by name or VID.""" test_config_condition(not isinstance(vlan_name, (str, int)), ('VLAN must be type %s or %s not %s' % (str, int, type(vlan_name)))) if vlan_name in vlan_by_name: return vlan_by_name[vlan_name] elif vlan_name in self.vlans: return self.vlans[vlan_name] return None def resolve_stack_dps(): """Resolve DP references in stacking config.""" port_stack_dp = {} for port in self.stack_ports: stack_dp = port.stack['dp'] test_config_condition(stack_dp not in dp_by_name, ('stack DP %s not defined' % stack_dp)) port_stack_dp[port] = dp_by_name[stack_dp] for port, dp in list(port_stack_dp.items()): port.stack['dp'] = dp stack_port = dp.resolve_port(port.stack['port']) test_config_condition(stack_port is None, ('stack port %s not defined in DP %s' % (port.stack['port'], dp.name))) port.stack['port'] = stack_port def resolve_mirror_destinations(): """Resolve mirror port references and destinations.""" mirror_from_port = defaultdict(list) for mirror_port in list(self.ports.values()): if mirror_port.mirror is not None: mirrored_ports = resolve_ports(mirror_port.mirror) test_config_condition( len(mirrored_ports) != len(mirror_port.mirror), ('port mirror not defined in DP %s' % self.name)) for mirrored_port in mirrored_ports: mirror_from_port[mirrored_port].append(mirror_port) # TODO: confusingly, mirror at config time means what ports to mirror from. # But internally we use as a list of ports to mirror to. for mirrored_port, mirror_ports in list(mirror_from_port.items()): mirrored_port.mirror = [] for mirror_port in mirror_ports: mirrored_port.mirror.append(mirror_port.number) mirror_port.output_only = True def resolve_override_output_ports(): """Resolve override output ports.""" for port_no, port in list(self.ports.items()): if port.override_output_port: port.override_output_port = self.resolve_port( port.override_output_port) test_config_condition( not port.override_output_port, ('override_output_port port not defined')) self.ports[port_no] = port def resolve_acl(acl_in): """Resolve an individual ACL.""" test_config_condition(acl_in not in self.acls, ('missing ACL %s on %s' % (self.name, acl_in))) acl = self.acls[acl_in] mirror_destinations = set() def resolve_meter(_acl, action_conf): meter_name = action_conf test_config_condition( meter_name not in self.meters, ('meter %s is not configured' % meter_name)) return action_conf def resolve_mirror(_acl, action_conf): port_name = action_conf port = self.resolve_port(port_name) # If this DP does not have this port, do nothing. if port is not None: action_conf = port.number mirror_destinations.add(port.number) return action_conf return None def resolve_output(_acl, action_conf): resolved_action_conf = {} test_config_condition(not isinstance(action_conf, dict), ('action conf is not a dictionary')) for output_action, output_action_values in list( action_conf.items()): if output_action == 'port': port_name = output_action_values port = self.resolve_port(port_name) test_config_condition( not port, ('output port not defined in DP: %s' % self.name)) resolved_action_conf[output_action] = port.number # pytype: disable=attribute-error elif output_action == 'ports': resolved_ports = resolve_port_numbers( output_action_values) test_config_condition( len(resolved_ports) != len(output_action_values), ('output port(s) not defined in DP: %s' % self.name)) resolved_action_conf[output_action] = resolved_ports elif output_action == 'failover': failover = output_action_values test_config_condition(not isinstance(failover, dict), ('failover is not a dictionary')) resolved_action_conf[output_action] = {} for failover_name, failover_values in list( failover.items()): if failover_name == 'ports': resolved_failover_values = resolve_port_numbers( failover_values) test_config_condition( len(resolved_failover_values) != len(failover_values), ('failover port(s) not defined in DP: %s' % self.name)) failover_values = resolved_failover_values resolved_action_conf[output_action][ failover_name] = failover_values else: resolved_action_conf[ output_action] = output_action_values if resolved_action_conf: return resolved_action_conf return None def resolve_noop(_acl, action_conf): return action_conf action_resolvers = { 'meter': resolve_meter, 'mirror': resolve_mirror, 'output': resolve_output, 'allow': resolve_noop, 'force_port_vlan': resolve_noop, } def build_acl(acl, vid=None): """Check that ACL can be built from config and mark mirror destinations.""" if acl.rules: try: ofmsgs = valve_acl.build_acl_ofmsgs( [acl], self.wildcard_table, valve_of.goto_table(self.wildcard_table), valve_of.goto_table(self.wildcard_table), 2**16 - 1, self.meters, acl.exact_match, vlan_vid=vid) test_config_condition(not ofmsgs, 'of messages is empty') for ofmsg in ofmsgs: ofmsg.datapath = NullRyuDatapath() ofmsg.set_xid(0) ofmsg.serialize() except (AddrFormatError, KeyError, ValueError) as err: raise InvalidConfigError(err) for port_no in mirror_destinations: port = self.ports[port_no] port.output_only = True for rule_conf in acl.rules: for attrib, attrib_value in list(rule_conf.items()): if attrib == 'actions': resolved_actions = {} test_config_condition( not isinstance(attrib_value, dict), ('attrib_value is not a dictionary')) for action_name, action_conf in list( attrib_value.items()): resolved_action_conf = action_resolvers[ action_name](acl, action_conf) test_config_condition( resolved_action_conf is None, ('cannot resolve ACL rule %s' % rule_conf)) resolved_actions[ action_name] = resolved_action_conf rule_conf[attrib] = resolved_actions build_acl(acl, vid=1) def verify_acl_exact_match(acls): for acl in acls: test_config_condition( acl.exact_match != acls[0].exact_match, ('ACLs when used together must have consistent exact_match' )) def resolve_acls(): """Resolve config references in ACLs.""" # TODO: move this config validation to ACL object. for vlan in list(self.vlans.values()): if vlan.acls_in: acls = [] for acl in vlan.acls_in: resolve_acl(acl) acls.append(self.acls[acl]) vlan.acls_in = acls verify_acl_exact_match(acls) for port in list(self.ports.values()): if port.acls_in: test_config_condition( self.dp_acls, ('dataplane ACLs cannot be used with port ACLs.')) acls = [] for acl in port.acls_in: resolve_acl(acl) acls.append(self.acls[acl]) port.acls_in = acls verify_acl_exact_match(acls) if self.dp_acls: acls = [] for acl in self.acls: resolve_acl(acl) acls.append(self.acls[acl]) self.dp_acls = acls def resolve_vlan_names_in_routers(): """Resolve VLAN references in routers.""" dp_routers = {} for router_name, router in list(self.routers.items()): vlans = [] for vlan_name in router.vlans: vlan = resolve_vlan(vlan_name) if vlan is not None: vlans.append(vlan) if len(vlans) > 1: dp_router = copy.copy(router) dp_router.vlans = vlans dp_routers[router_name] = dp_router self.routers = dp_routers test_config_condition( not self.vlans, 'no VLANs referenced by interfaces in %s' % self.name) for dp in dps: dp_by_name[dp.name] = dp for vlan in list(self.vlans.values()): vlan_by_name[vlan.name] = vlan resolve_stack_dps() resolve_mirror_destinations() resolve_override_output_ports() resolve_vlan_names_in_routers() resolve_acls() bgp_vlans = self.bgp_vlans() if bgp_vlans: for vlan in bgp_vlans: vlan_dps = [dp for dp in dps if vlan.vid in dp.vlans] test_config_condition( len(vlan_dps) != 1, ('DPs %s sharing a BGP speaker VLAN is unsupported')) router_ids = set([vlan.bgp_routerid for vlan in bgp_vlans]) test_config_condition( len(router_ids) != 1, 'BGP router IDs must all be the same') bgp_ports = set([vlan.bgp_port for vlan in bgp_vlans]) test_config_condition( len(bgp_ports) != 1, 'BGP ports must all be the same') for vlan in bgp_vlans: test_config_condition( vlan.bgp_server_addresses != (bgp_vlans[0].bgp_server_addresses), ('BGP server addresses must all be the same')) for port in list(self.ports.values()): port.finalize() for vlan in list(self.vlans.values()): vlan.finalize() for acl in list(self.acls.values()): acl.finalize() for router in list(self.routers.values()): router.finalize() self.finalize()
def verify_acl_exact_match(acls): for acl in acls: test_config_condition( acl.exact_match != acls[0].exact_match, ('ACLs when used together must have consistent exact_match' ))
def check_config(self): super(Router, self).check_config() test_config_condition( not (isinstance(self.vlans, list) and len(self.vlans) > 1), ('router %s must have at least 2 VLANs configured' % self))
def check_config(self): super(VLAN, self).check_config() test_config_condition(not self.vid_valid(self.vid), 'invalid VID %s' % self.vid) test_config_condition(not netaddr.valid_mac(self.faucet_mac), ( 'invalid MAC address %s' % self.faucet_mac)) test_config_condition(self.acl_in and self.acls_in, 'found both acl_in and acls_in, use only acls_in') if self.acl_in and not isinstance(self.acl_in, list): self.acls_in = [self.acl_in,] self.acl_in = None if self.acls_in: for acl in self.acls_in: test_config_condition(not isinstance(acl, (int, str)), 'acl names must be int or str') if self.max_hosts: if not self.proactive_arp_limit: self.proactive_arp_limit = 2 * self.max_hosts if not self.proactive_nd_limit: self.proactive_nd_limit = 2 * self.max_hosts if self.faucet_vips: self.faucet_vips = frozenset([ self._check_ip_str(ip_str, ip_method=ipaddress.ip_interface) for ip_str in self.faucet_vips]) if self.bgp_neighbor_addresses or self.bgp_neighbour_addresses: neigh_addresses = frozenset(self.bgp_neighbor_addresses + self.bgp_neighbour_addresses) self.bgp_neighbor_addresses = frozenset([ self._check_ip_str(ip_str) for ip_str in neigh_addresses]) if self.bgp_server_addresses: self.bgp_server_addresses = frozenset([ self._check_ip_str(ip_str) for ip_str in self.bgp_server_addresses]) for ipv in self.bgp_ipvs(): test_config_condition( len(self.bgp_server_addresses_by_ipv(ipv)) != 1, 'Only one BGP server address per IP version supported') if self.bgp_as: test_config_condition(not isinstance(self.bgp_port, int), ( 'BGP port must be %s not %s' % (int, type(self.bgp_port)))) test_config_condition(self.bgp_connect_mode not in ('passive'), ( 'BGP connect mode %s must be passive' % self.bgp_connect_mode)) test_config_condition(not ipaddress.IPv4Address(self.bgp_routerid), ( '%s is not a valid IPv4 address' % (self.bgp_routerid))) test_config_condition(not self.bgp_neighbor_as, 'No BGP neighbor AS') test_config_condition(not self.bgp_neighbor_addresses, 'No BGP neighbor addresses') test_config_condition(len(self.bgp_neighbor_addresses) != len(self.bgp_neighbor_addresses), ( 'Must be as many BGP neighbor addresses as BGP server addresses')) if self.routes: test_config_condition(not isinstance(self.routes, list), 'invalid VLAN routes format') try: self.routes = [route['route'] for route in self.routes] except TypeError: raise InvalidConfigError('%s is not a valid routes value' % self.routes) except KeyError: pass for route in self.routes: test_config_condition(not isinstance(route, dict), 'invalid VLAN route format') test_config_condition('ip_gw' not in route, 'missing ip_gw in VLAN route') test_config_condition('ip_dst' not in route, 'missing ip_dst in VLAN route') ip_gw = self._check_ip_str(route['ip_gw']) ip_dst = self._check_ip_str(route['ip_dst'], ip_method=ipaddress.ip_network) test_config_condition( ip_gw.version != ip_dst.version, 'ip_gw version does not match the ip_dst version') self.add_route(ip_dst, ip_gw)
def finalize(self): if self.native_vlan: test_config_condition(self.native_vlan in self.tagged_vlans, ( 'cannot have same native and tagged VLAN on same port')) self.tagged_vlans = tuple(self.tagged_vlans) super(Port, self).finalize()
def check_config(self): super(DP, self).check_config() test_config_condition(not isinstance(self.dp_id, int), ('dp_id must be %s not %s' % (int, type(self.dp_id)))) test_config_condition(self.dp_id < 0 or self.dp_id > 2**64 - 1, ('DP ID %s not in valid range' % self.dp_id)) test_config_condition(not netaddr.valid_mac(self.faucet_dp_mac), ('invalid MAC address %s' % self.faucet_dp_mac)) test_config_condition( not (self.interfaces or self.interface_ranges), ('DP %s must have at least one interface' % self)) test_config_condition(self.timeout < 15, ('timeout must be > 15')) # To prevent L2 learning from timing out before L3 can refresh test_config_condition( not (self.arp_neighbor_timeout < (self.timeout / 2)), ('L2 timeout must be > ARP timeout * 2')) test_config_condition( not (self.nd_neighbor_timeout < (self.timeout / 2)), ('L2 timeout must be > ND timeout * 2')) if self.cache_update_guard_time == 0: self.cache_update_guard_time = int(self.timeout / 2) if self.learn_jitter == 0: self.learn_jitter = int(max(math.sqrt(self.timeout) * 3, 1)) if self.learn_ban_timeout == 0: self.learn_ban_timeout = self.learn_jitter if self.lldp_beacon: self._check_conf_types(self.lldp_beacon, self.lldp_beacon_defaults_types) test_config_condition('send_interval' not in self.lldp_beacon, ('lldp_beacon send_interval not set')) test_config_condition('max_per_interval' not in self.lldp_beacon, ('lldp_beacon max_per_interval not set')) self.lldp_beacon = self._set_unknown_conf( self.lldp_beacon, self.lldp_beacon_defaults_types) if self.lldp_beacon['system_name'] is None: self.lldp_beacon['system_name'] = self.name if self.stack: self._check_conf_types(self.stack, self.stack_defaults_types) if self.dot1x: self._check_conf_types(self.dot1x, self.dot1x_defaults_types) self._check_conf_types(self.table_sizes, self.default_table_sizes_types)
def build(self, meters, vid, port_num): """Check that ACL can be built from config.""" self.matches = {} self.set_fields = set() self.meter = False if self.rules: try: ofmsgs = valve_acl.build_acl_ofmsgs( [self], wildcard_table, [valve_of.goto_table(wildcard_table)], [valve_of.goto_table(wildcard_table)], 2**16-1, meters, self.exact_match, vlan_vid=vid, port_num=port_num) except (netaddr.core.AddrFormatError, KeyError, ValueError) as err: raise InvalidConfigError from err test_config_condition(not ofmsgs, 'OF messages is empty') for ofmsg in ofmsgs: try: valve_of.verify_flowmod(ofmsg) except (KeyError, ValueError) as err: raise InvalidConfigError from err except Exception as err: raise err if valve_of.is_flowmod(ofmsg): apply_actions = [] for inst in ofmsg.instructions: if valve_of.is_apply_actions(inst): apply_actions.extend(inst.actions) elif valve_of.is_meter(inst): self.meter = True for action in apply_actions: if valve_of.is_set_field(action): self.set_fields.add(action.key) for match, value in ofmsg.match.items(): has_mask = isinstance(value, (tuple, list)) if has_mask or match not in self.matches: self.matches[match] = has_mask for tunnel_rules in self.tunnel_dests.values(): if 'exit_instructions' in tunnel_rules: exit_inst = tunnel_rules['exit_instructions'] try: ofmsgs = valve_acl.build_tunnel_ofmsgs( exit_inst, wildcard_table, 1) except (netaddr.core.AddrFormatError, KeyError, ValueError) as err: raise InvalidConfigError from err test_config_condition(not ofmsgs, 'OF messages is empty') for ofmsg in ofmsgs: try: valve_of.verify_flowmod(ofmsg) except (KeyError, ValueError) as err: raise InvalidConfigError from err except Exception as err: raise err if valve_of.is_flowmod(ofmsg): apply_actions = [] for inst in ofmsg.instructions: if valve_of.is_apply_actions(inst): apply_actions.extend(inst.actions) elif valve_of.is_meter(inst): self.meter = True for action in apply_actions: if valve_of.is_set_field(action): self.set_fields.add(action.key) for match, value in ofmsg.match.items(): has_mask = isinstance(value, (tuple, list)) if has_mask or match not in self.matches: self.matches[match] = has_mask return (self.matches, self.set_fields, self.meter)
def check_config(self): super(Port, self).check_config() test_config_condition(not (isinstance(self.number, int) and self.number > 0 and ( not valve_of.ignore_port(self.number))), ('Port number invalid: %s' % self.number)) non_vlan_options = {'stack', 'mirror', 'coprocessor', 'output_only'} vlan_agnostic_options = {'enabled', 'number', 'name', 'description', 'max_lldp_lost'} vlan_port = self.tagged_vlans or self.native_vlan non_vlan_port_options = {option for option in non_vlan_options if getattr(self, option)} test_config_condition( vlan_port and non_vlan_port_options, 'cannot have VLANs configured on non-VLAN ports: %s' % self) if self.output_only: test_config_condition( not non_vlan_port_options.issubset({'mirror', 'output_only'}), 'output_only can only coexist with mirror option on same port %s' % self) elif self.mirror: test_config_condition( not non_vlan_port_options.issubset({'mirror', 'coprocessor'}), 'coprocessor can only coexist with mirror option on same port %s' % self) else: test_config_condition( len(non_vlan_port_options) > 1, 'cannot have multiple non-VLAN port options %s on same port: %s' % ( non_vlan_port_options, self)) if non_vlan_port_options: for key, default_val in self.defaults.items(): if key in vlan_agnostic_options or key in non_vlan_port_options: continue if key.startswith('acl') and (self.stack or self.coprocessor): continue val = getattr(self, key) test_config_condition( val != default_val and val, 'Cannot have VLAN option %s: %s on non-VLAN port %s' % (key, val, self)) test_config_condition( self.hairpin and self.hairpin_unicast, 'Cannot have both hairpin and hairpin_unicast enabled') dot1x_features = { dot1x_feature for dot1x_feature, dot1x_config in self.defaults.items() if dot1x_feature.startswith('dot1x_') and dot1x_config} test_config_condition( dot1x_features and not self.dot1x, '802.1x features %s require port to have dot1x enabled' % dot1x_features) if self.dot1x: test_config_condition(self.number > 65535, ( '802.1x not supported on ports > 65535')) if self.dot1x_mab: test_config_condition(self.dot1x_dyn_acl, ( '802.1x_MAB cannot be used with 802.1x_DYN_ACL')) if self.dot1x_dyn_acl: test_config_condition(self.dot1x_acl, ( '802.1x_DYN_ACL cannot be used with 802.1x_ACL')) if self.coprocessor: self._check_conf_types(self.coprocessor, self.coprocessor_defaults_types) test_config_condition( self.coprocessor.get('strategy', None) != 'vlan_vid', 'coprocessor only supports vlan_vid strategy') self.coprocessor['vlan_vid_base'] = self.coprocessor.get('vlan_vid_base', 1000) if self.stack: self._check_conf_types(self.stack, self.stack_defaults_types) for stack_config in list(self.stack_defaults_types.keys()): test_config_condition(stack_config not in self.stack, ( 'stack %s must be defined' % stack_config)) # LLDP always enabled for stack ports. self.receive_lldp = True if not self.lldp_beacon_enabled(): self.lldp_beacon.update({'enable': True}) if self.lacp_resp_interval is not None: test_config_condition( self.lacp_resp_interval > 65535 or self.lacp_resp_interval < 0.3, ('interval must be at least 0.3 and less than 65536')) if self.lacp_port_priority is not None: test_config_condition( self.lacp_port_priority > 255 or self.lacp_port_priority < 0, ('lacp port priority must be at least 0 and less than 256')) if self.lldp_peer_mac: test_config_condition(not netaddr.valid_mac(self.lldp_peer_mac), ( 'invalid MAC address %s' % self.lldp_peer_mac)) if self.lldp_beacon: self._check_conf_types( self.lldp_beacon, self.lldp_beacon_defaults_types) self.lldp_beacon = self._set_unknown_conf( self.lldp_beacon, self.lldp_beacon_defaults_types) if self.lldp_beacon_enabled(): if self.lldp_beacon['port_descr'] is None: self.lldp_beacon['port_descr'] = self.description org_tlvs = [] for org_tlv in self.lldp_beacon['org_tlvs']: self._check_conf_types(org_tlv, self.lldp_org_tlv_defaults_types) test_config_condition(len(org_tlv) != len(self.lldp_org_tlv_defaults_types), ( 'missing org_tlv config')) if not isinstance(org_tlv['info'], bytearray): try: org_tlv['info'] = bytearray.fromhex( org_tlv['info']) # pytype: disable=missing-parameter except ValueError: org_tlv['info'] = org_tlv['info'].encode('utf-8') if not isinstance(org_tlv['oui'], bytearray): org_tlv['oui'] = bytearray.fromhex( '%6.6x' % org_tlv['oui']) # pytype: disable=missing-parameter org_tlvs.append(org_tlv) self.lldp_beacon['org_tlvs'] = org_tlvs test_config_condition( self.acl_in and self.acls_in, 'Found both acl_in and acls_in, use only acls_in') if self.acl_in and not isinstance(self.acl_in, list): self.acls_in = [self.acl_in] self.acl_in = None if self.acls_in: for acl in self.acls_in: test_config_condition(not isinstance(acl, (int, str)), 'ACL names must be int or str') lacp_options = [self.lacp_selected, self.lacp_unselected, self.lacp_standby] test_config_condition( lacp_options.count(True) > 1, 'Cannot force multiple LACP port selection states')
def check_config(self): test_config_condition(not isinstance(self.dp_id, int), ('dp_id must be %s not %s' % (int, type(self.dp_id)))) test_config_condition(self.dp_id < 0 or self.dp_id > 2**64 - 1, ('DP ID %s not in valid range' % self.dp_id)) test_config_condition(not netaddr.valid_mac(self.faucet_dp_mac), ('invalid MAC address %s' % self.faucet_dp_mac)) test_config_condition(self.group_table and self.group_table_routing, ( 'groups for routing and other functions simultaneously not supported' )) test_config_condition( not (self.interfaces or self.interface_ranges), ('DP %s must have at least one interface' % self)) # To prevent L2 learning from timing out before L3 can refresh test_config_condition(self.timeout < self.arp_neighbor_timeout, ('L2 timeout must be >= L3 timeout')) if self.lldp_beacon: self._check_conf_types(self.lldp_beacon, self.lldp_beacon_defaults_types) test_config_condition('send_interval' not in self.lldp_beacon, ('lldp_beacon send_interval not set')) test_config_condition('max_per_interval' not in self.lldp_beacon, ('lldp_beacon max_per_interval not set')) self.lldp_beacon = self._set_unknown_conf( self.lldp_beacon, self.lldp_beacon_defaults_types) if self.lldp_beacon['system_name'] is None: self.lldp_beacon['system_name'] = self.name if self.stack: self._check_conf_types(self.stack, self.stack_defaults_types) if self.dot1x: self._check_conf_types(self.dot1x, self.dot1x_defaults_types) if self.table_sizes: self._check_conf_types(self.table_sizes, self.table_sizes_types)
def check_config(self): super(Port, self).check_config() test_config_condition( not (isinstance(self.number, int) and self.number > 0 and (not valve_of.ignore_port(self.number))), ('Port number invalid: %s' % self.number)) test_config_condition( self.hairpin and self.hairpin_unicast, 'Cannot have both hairpin and hairpin_unicast enabled') if self.dot1x: test_config_condition(self.number > 65535, ('802.1x not supported on ports > 65535')) if self.dot1x_acl: test_config_condition( not self.dot1x, ('802.1x_ACL requires dot1x to be enabled also')) if self.mirror: test_config_condition( self.tagged_vlans or self.native_vlan, ('mirror port %s cannot have any VLANs assigned' % self)) if self.stack: self._check_conf_types(self.stack, self.stack_defaults_types) for stack_config in list(self.stack_defaults_types.keys()): test_config_condition( stack_config not in self.stack, ('stack %s must be defined' % stack_config)) # LLDP always enabled for stack ports. self.receive_lldp = True if not self.lldp_beacon_enabled(): self.lldp_beacon.update({'enable': True}) if self.lldp_beacon: self._check_conf_types(self.lldp_beacon, self.lldp_beacon_defaults_types) self.lldp_beacon = self._set_unknown_conf( self.lldp_beacon, self.lldp_beacon_defaults_types) if self.lldp_beacon_enabled(): if self.lldp_beacon['port_descr'] is None: self.lldp_beacon['port_descr'] = self.description org_tlvs = [] for org_tlv in self.lldp_beacon['org_tlvs']: self._check_conf_types(org_tlv, self.lldp_org_tlv_defaults_types) test_config_condition( len(org_tlv) != len(self.lldp_org_tlv_defaults_types), ('missing org_tlv config')) if not isinstance(org_tlv['info'], bytearray): try: org_tlv['info'] = bytearray.fromhex( org_tlv['info']) # pytype: disable=missing-parameter except ValueError: org_tlv['info'] = org_tlv['info'].encode('utf-8') if not isinstance(org_tlv['oui'], bytearray): org_tlv['oui'] = bytearray.fromhex( '%6.6x' % org_tlv['oui']) # pytype: disable=missing-parameter org_tlvs.append(org_tlv) self.lldp_beacon['org_tlvs'] = org_tlvs if self.acl_in and self.acls_in: raise InvalidConfigError( 'found both acl_in and acls_in, use only acls_in') if self.acl_in and not isinstance(self.acl_in, list): self.acls_in = [ self.acl_in, ] self.acl_in = None if self.acls_in: for acl in self.acls_in: test_config_condition(not isinstance(acl, (int, str)), 'ACL names must be int or str')
def finalize_config(self, dps): """Perform consistency checks after initial config parsing.""" dp_by_name = {} vlan_by_name = {} def resolve_ports(port_names): """Resolve list of ports, by port by name or number.""" resolved_ports = [] for port_name in port_names: port = self.resolve_port(port_name) if port is not None: resolved_ports.append(port) return resolved_ports def resolve_port_numbers(port_names): """Resolve list of ports to numbers, by port by name or number.""" return [port.number for port in resolve_ports(port_names)] def resolve_vlan(vlan_name): """Resolve VLAN by name or VID.""" test_config_condition(not isinstance(vlan_name, (str, int)), ('VLAN must be type %s or %s not %s' % (str, int, type(vlan_name)))) if vlan_name in vlan_by_name: return vlan_by_name[vlan_name] if vlan_name in self.vlans: return self.vlans[vlan_name] return None def resolve_stack_dps(): """Resolve DP references in stacking config.""" port_stack_dp = {} for port in self.stack_ports: stack_dp = port.stack['dp'] test_config_condition(stack_dp not in dp_by_name, ('stack DP %s not defined' % stack_dp)) port_stack_dp[port] = dp_by_name[stack_dp] for port, dp in list(port_stack_dp.items()): port.stack['dp'] = dp stack_port = dp.resolve_port(port.stack['port']) test_config_condition(stack_port is None, ('stack port %s not defined in DP %s' % (port.stack['port'], dp.name))) port.stack['port'] = stack_port def resolve_mirror_destinations(): """Resolve mirror port references and destinations.""" mirror_from_port = defaultdict(list) for mirror_port in list(self.ports.values()): if mirror_port.mirror is not None: mirrored_ports = resolve_ports(mirror_port.mirror) test_config_condition( len(mirrored_ports) != len(mirror_port.mirror), ('port mirror not defined in DP %s' % self.name)) for mirrored_port in mirrored_ports: mirror_from_port[mirrored_port].append(mirror_port) # TODO: confusingly, mirror at config time means what ports to mirror from. # But internally we use as a list of ports to mirror to. for mirrored_port, mirror_ports in list(mirror_from_port.items()): mirrored_port.mirror = [] for mirror_port in mirror_ports: mirrored_port.mirror.append(mirror_port.number) mirror_port.output_only = True def resolve_override_output_ports(): """Resolve override output ports.""" for port_no, port in list(self.ports.items()): if port.override_output_port: port.override_output_port = self.resolve_port( port.override_output_port) test_config_condition( not port.override_output_port, ('override_output_port port not defined')) self.ports[port_no] = port def resolve_acl(acl_in, vid): """Resolve an individual ACL.""" test_config_condition(acl_in not in self.acls, ('missing ACL %s in DP: %s' % (acl_in, self.name))) acl = self.acls[acl_in] mirror_destinations = set() def resolve_meter(_acl, action_conf): meter_name = action_conf test_config_condition( meter_name not in self.meters, ('meter %s is not configured' % meter_name)) return action_conf def resolve_mirror(_acl, action_conf): port_name = action_conf port = self.resolve_port(port_name) # If this DP does not have this port, do nothing. if port is not None: action_conf = port.number mirror_destinations.add(port.number) return action_conf return None def resolve_output(_acl, action_conf): resolved_action_conf = {} test_config_condition(not isinstance(action_conf, dict), ('action conf is not a dictionary')) for output_action, output_action_values in list( action_conf.items()): if output_action == 'port': port_name = output_action_values port = self.resolve_port(port_name) test_config_condition( not port, ('output port not defined in DP: %s' % self.name)) resolved_action_conf[output_action] = port.number # pytype: disable=attribute-error elif output_action == 'ports': resolved_ports = resolve_port_numbers( output_action_values) test_config_condition( len(resolved_ports) != len(output_action_values), ('output port(s) not defined in DP: %s' % self.name)) resolved_action_conf[output_action] = resolved_ports elif output_action == 'failover': failover = output_action_values test_config_condition(not isinstance(failover, dict), ('failover is not a dictionary')) resolved_action_conf[output_action] = {} for failover_name, failover_values in list( failover.items()): if failover_name == 'ports': resolved_failover_values = resolve_port_numbers( failover_values) test_config_condition( len(resolved_failover_values) != len(failover_values), ('failover port(s) not defined in DP: %s' % self.name)) failover_values = resolved_failover_values resolved_action_conf[output_action][ failover_name] = failover_values else: resolved_action_conf[ output_action] = output_action_values if resolved_action_conf: return resolved_action_conf return None def resolve_noop(_acl, action_conf): return action_conf action_resolvers = { 'meter': resolve_meter, 'mirror': resolve_mirror, 'output': resolve_output, 'allow': resolve_noop, 'force_port_vlan': resolve_noop, } def build_acl(acl, vid): """Check that ACL can be built from config and mark mirror destinations.""" matches = {} set_fields = set() meter = False if acl.rules: try: ofmsgs = valve_acl.build_acl_ofmsgs( [acl], self.wildcard_table, valve_of.goto_table(self.wildcard_table), valve_of.goto_table(self.wildcard_table), 2**16 - 1, self.meters, acl.exact_match, vlan_vid=vid) except (netaddr.core.AddrFormatError, KeyError, ValueError) as err: raise InvalidConfigError(err) test_config_condition(not ofmsgs, 'OF messages is empty') for ofmsg in ofmsgs: ofmsg.datapath = NullRyuDatapath() ofmsg.set_xid(0) try: ofmsg.serialize() except (KeyError, ValueError) as err: raise InvalidConfigError(err) if valve_of.is_flowmod(ofmsg): apply_actions = [] for inst in ofmsg.instructions: if valve_of.is_apply_actions(inst): apply_actions.extend(inst.actions) elif valve_of.is_meter(inst): meter = True for action in apply_actions: if valve_of.is_set_field(action): set_fields.add(action.key) for match, value in list(ofmsg.match.items()): has_mask = isinstance(value, (tuple, list)) if has_mask or match not in matches: matches[match] = has_mask for port_no in mirror_destinations: port = self.ports[port_no] port.output_only = True return (matches, set_fields, meter) for rule_conf in acl.rules: for attrib, attrib_value in list(rule_conf.items()): if attrib == 'actions': resolved_actions = {} test_config_condition( not isinstance(attrib_value, dict), ('attrib_value is not a dictionary')) for action_name, action_conf in list( attrib_value.items()): resolved_action_conf = action_resolvers[ action_name](acl, action_conf) test_config_condition( resolved_action_conf is None, ('cannot resolve ACL rule %s' % rule_conf)) resolved_actions[ action_name] = resolved_action_conf rule_conf[attrib] = resolved_actions return build_acl(acl, vid) def verify_acl_exact_match(acls): for acl in acls: test_config_condition( acl.exact_match != acls[0].exact_match, ('ACLs when used together must have consistent exact_match' )) return acls[0].exact_match def resolve_acls(): """Resolve config references in ACLs.""" # TODO: move this config validation to ACL object. port_acl_matches = {} port_acl_set_fields = set() port_acl_exact_match = False port_acl_matches.update({'in_port': False}) port_acl_meter = False vlan_acl_matches = {} vlan_acl_exact_match = False vlan_acl_set_fields = set() vlan_acl_meter = False def merge_matches(matches, new_matches): for field, has_mask in list(new_matches.items()): if has_mask or field not in matches: matches[field] = has_mask for vlan in list(self.vlans.values()): if vlan.acls_in: acls = [] for acl in vlan.acls_in: matches, set_fields, meter = resolve_acl(acl, vlan.vid) merge_matches(vlan_acl_matches, matches) vlan_acl_set_fields = vlan_acl_set_fields.union( set_fields) if meter: vlan_acl_meter = True acls.append(self.acls[acl]) vlan.acls_in = acls vlan_acl_exact_match = verify_acl_exact_match(acls) for port in list(self.ports.values()): if port.acls_in: test_config_condition( self.dp_acls, ('dataplane ACLs cannot be used with port ACLs.')) acls = [] for acl in port.acls_in: matches, set_fields, meter = resolve_acl(acl, None) merge_matches(port_acl_matches, matches) port_acl_set_fields = port_acl_set_fields.union( set_fields) if meter: port_acl_meter = True acls.append(self.acls[acl]) port.acls_in = acls port_acl_exact_match = verify_acl_exact_match(acls) if self.dp_acls: acls = [] for acl in self.acls: matches, set_fields, meter = resolve_acl(acl, None) merge_matches(port_acl_matches, matches) port_acl_set_fields = port_acl_set_fields.union(set_fields) if meter: port_acl_meter = True acls.append(self.acls[acl]) self.dp_acls = acls port_acl_matches = { (field, mask) for field, mask in list(port_acl_matches.items()) } vlan_acl_matches = { (field, mask) for field, mask in list(vlan_acl_matches.items()) } # TODO: skip port_acl table if not configured. # TODO: dynamically configure output attribue override_table_config = { 'port_acl': ValveTableConfig('port_acl', exact_match=port_acl_exact_match, meter=port_acl_meter, output=True, match_types=port_acl_matches, set_fields=tuple(port_acl_set_fields)), 'vlan_acl': ValveTableConfig('vlan_acl', exact_match=vlan_acl_exact_match, meter=vlan_acl_meter, output=True, match_types=vlan_acl_matches, set_fields=tuple(vlan_acl_set_fields)), } return override_table_config def resolve_vlan_names_in_routers(): """Resolve VLAN references in routers.""" dp_routers = {} for router_name, router in list(self.routers.items()): vlans = [] for vlan_name in router.vlans: vlan = resolve_vlan(vlan_name) if vlan is not None: vlans.append(vlan) if len(vlans) > 1: dp_router = copy.copy(router) dp_router.vlans = vlans dp_routers[router_name] = dp_router vips = set() for vlan in vlans: for vip in vlan.faucet_vips: if (vip.ip in valve_packet.IPV4_LINK_LOCAL or vip.ip in valve_packet.IPV6_LINK_LOCAL): continue vips.add(vip) for vip in vips: for other_vip in vips - set([vip]): test_config_condition( vip.ip in other_vip.network, 'VIPs %s and %s overlap in router %s' % (vip, other_vip, router_name)) self.routers = dp_routers test_config_condition( not self.vlans, 'no VLANs referenced by interfaces in %s' % self.name) for dp in dps: dp_by_name[dp.name] = dp for vlan in list(self.vlans.values()): vlan_by_name[vlan.name] = vlan resolve_stack_dps() resolve_mirror_destinations() resolve_override_output_ports() resolve_vlan_names_in_routers() override_table_config = resolve_acls() # Only configure IP routing tables if enabled. ipvs = set() for vlan in list(self.vlans.values()): ipvs = ipvs.union(vlan.ipvs()) for ipv in (4, 6): if ipv not in ipvs: table_name = 'ipv%u_fib' % ipv override_table_config[table_name] = ValveTableConfig( table_name) if not ipvs: override_table_config['vip'] = ValveTableConfig('vip') self._configure_tables(override_table_config) bgp_vlans = self.bgp_vlans() if bgp_vlans: for vlan in bgp_vlans: vlan_dps = [dp for dp in dps if vlan.vid in dp.vlans] test_config_condition( len(vlan_dps) != 1, ('DPs %s sharing a BGP speaker VLAN is unsupported')) router_ids = {vlan.bgp_routerid for vlan in bgp_vlans} test_config_condition( len(router_ids) != 1, 'BGP router IDs must all be the same') bgp_ports = {vlan.bgp_port for vlan in bgp_vlans} test_config_condition( len(bgp_ports) != 1, 'BGP ports must all be the same') for vlan in bgp_vlans: test_config_condition( vlan.bgp_server_addresses != (bgp_vlans[0].bgp_server_addresses), ('BGP server addresses must all be the same')) for port in list(self.ports.values()): port.finalize() for vlan in list(self.vlans.values()): vlan.finalize() for acl in list(self.acls.values()): acl.finalize() for router in list(self.routers.values()): router.finalize() self.finalize()
def resolve_stack_topology(self, dps): """Resolve inter-DP config for stacking.""" root_dp = None stack_dps = [] for dp in dps: if dp.stack is not None: stack_dps.append(dp) if 'priority' in dp.stack: test_config_condition(dp.stack['priority'] <= 0, ('stack priority must be > 0')) test_config_condition(root_dp is not None, 'cannot have multiple stack roots') root_dp = dp for vlan in list(dp.vlans.values()): test_config_condition( vlan.faucet_vips, ('routing + stacking not supported')) if root_dp is None: test_config_condition(stack_dps, 'stacking enabled but no root_dp') return edge_count = {} graph = networkx.MultiGraph() for dp in dps: if dp.stack_ports: graph.add_node(dp.name) for port in dp.stack_ports: edge_name = self.add_stack_link(graph, dp, port) if edge_name not in edge_count: edge_count[edge_name] = 0 edge_count[edge_name] += 1 if graph.size(): for edge_name, count in list(edge_count.items()): test_config_condition( count != 2, '%s defined only in one direction' % edge_name) if self.name in graph: if self.stack is None: self.stack = {} self.stack['root_dp'] = root_dp self.stack['graph'] = graph longest_path_to_root_len = 0 for dp in graph.nodes(): path_to_root_len = len( self.shortest_path(root_dp.name, src_dp=dp)) test_config_condition(path_to_root_len == 0, '%s not connected to stack' % dp) longest_path_to_root_len = max(path_to_root_len, longest_path_to_root_len) self.stack[ 'longest_path_to_root_len'] = longest_path_to_root_len
def check_config(self): super(WatcherConf, self).check_config() test_config_condition(self.all_dps and self.dps is not None, 'all_dps and dps cannot be set together')
def resolve_acl(acl_in, vid): """Resolve an individual ACL.""" test_config_condition(acl_in not in self.acls, ('missing ACL %s in DP: %s' % (acl_in, self.name))) acl = self.acls[acl_in] mirror_destinations = set() def resolve_meter(_acl, action_conf): meter_name = action_conf test_config_condition( meter_name not in self.meters, ('meter %s is not configured' % meter_name)) return action_conf def resolve_mirror(_acl, action_conf): port_name = action_conf port = self.resolve_port(port_name) # If this DP does not have this port, do nothing. if port is not None: action_conf = port.number mirror_destinations.add(port.number) return action_conf return None def resolve_output(_acl, action_conf): resolved_action_conf = {} test_config_condition(not isinstance(action_conf, dict), ('action conf is not a dictionary')) for output_action, output_action_values in list( action_conf.items()): if output_action == 'port': port_name = output_action_values port = self.resolve_port(port_name) test_config_condition( not port, ('output port not defined in DP: %s' % self.name)) resolved_action_conf[output_action] = port.number # pytype: disable=attribute-error elif output_action == 'ports': resolved_ports = resolve_port_numbers( output_action_values) test_config_condition( len(resolved_ports) != len(output_action_values), ('output port(s) not defined in DP: %s' % self.name)) resolved_action_conf[output_action] = resolved_ports elif output_action == 'failover': failover = output_action_values test_config_condition(not isinstance(failover, dict), ('failover is not a dictionary')) resolved_action_conf[output_action] = {} for failover_name, failover_values in list( failover.items()): if failover_name == 'ports': resolved_failover_values = resolve_port_numbers( failover_values) test_config_condition( len(resolved_failover_values) != len(failover_values), ('failover port(s) not defined in DP: %s' % self.name)) failover_values = resolved_failover_values resolved_action_conf[output_action][ failover_name] = failover_values else: resolved_action_conf[ output_action] = output_action_values if resolved_action_conf: return resolved_action_conf return None def resolve_noop(_acl, action_conf): return action_conf action_resolvers = { 'meter': resolve_meter, 'mirror': resolve_mirror, 'output': resolve_output, 'allow': resolve_noop, 'force_port_vlan': resolve_noop, } def build_acl(acl, vid): """Check that ACL can be built from config and mark mirror destinations.""" matches = {} set_fields = set() meter = False if acl.rules: try: ofmsgs = valve_acl.build_acl_ofmsgs( [acl], self.wildcard_table, valve_of.goto_table(self.wildcard_table), valve_of.goto_table(self.wildcard_table), 2**16 - 1, self.meters, acl.exact_match, vlan_vid=vid) except (netaddr.core.AddrFormatError, KeyError, ValueError) as err: raise InvalidConfigError(err) test_config_condition(not ofmsgs, 'OF messages is empty') for ofmsg in ofmsgs: ofmsg.datapath = NullRyuDatapath() ofmsg.set_xid(0) try: ofmsg.serialize() except (KeyError, ValueError) as err: raise InvalidConfigError(err) if valve_of.is_flowmod(ofmsg): apply_actions = [] for inst in ofmsg.instructions: if valve_of.is_apply_actions(inst): apply_actions.extend(inst.actions) elif valve_of.is_meter(inst): meter = True for action in apply_actions: if valve_of.is_set_field(action): set_fields.add(action.key) for match, value in list(ofmsg.match.items()): has_mask = isinstance(value, (tuple, list)) if has_mask or match not in matches: matches[match] = has_mask for port_no in mirror_destinations: port = self.ports[port_no] port.output_only = True return (matches, set_fields, meter) for rule_conf in acl.rules: for attrib, attrib_value in list(rule_conf.items()): if attrib == 'actions': resolved_actions = {} test_config_condition( not isinstance(attrib_value, dict), ('attrib_value is not a dictionary')) for action_name, action_conf in list( attrib_value.items()): resolved_action_conf = action_resolvers[ action_name](acl, action_conf) test_config_condition( resolved_action_conf is None, ('cannot resolve ACL rule %s' % rule_conf)) resolved_actions[ action_name] = resolved_action_conf rule_conf[attrib] = resolved_actions return build_acl(acl, vid)
def _dp_parser_v2(acls_conf, dps_conf, meters_conf, routers_conf, vlans_conf): dps = [] def _get_vlan_by_key(dp_id, vlan_key, vlans): test_config_condition( not isinstance(vlan_key, (str, int)), ('VLAN key must not be type %s' % type(vlan_key))) if vlan_key in vlans: return vlans[vlan_key] for vlan in list(vlans.values()): if vlan_key == str(vlan.vid): return vlan # Create VLAN with VID, if not defined. return vlans.setdefault(vlan_key, VLAN(vlan_key, dp_id)) def _dp_parse_port(dp_id, port_key, port_conf, vlans): port = Port(port_key, dp_id, port_conf) test_config_condition( str(port_key) not in (str(port.number), port.name), ('Port key %s match port name or port number' % port_key)) def _dp_parse_native_port_vlan(): if port.native_vlan is not None: vlan = _get_vlan_by_key(dp_id, port.native_vlan, vlans) port.native_vlan = vlan def _dp_parse_tagged_port_vlans(): if port.tagged_vlans: port_tagged_vlans = [ _get_vlan_by_key(dp_id, vlan_key, vlans) for vlan_key in port.tagged_vlans ] port.tagged_vlans = port_tagged_vlans _dp_parse_native_port_vlan() _dp_parse_tagged_port_vlans() return port def _dp_add_ports(dp, dp_conf, dp_id, vlans): ports_conf = dp_conf.get('interfaces', {}) port_ranges_conf = dp_conf.get('interface_ranges', {}) # as users can config port vlan by using vlan name, we store vid in # Port instance instead of vlan name for data consistency test_config_condition(not isinstance(ports_conf, dict), ('Invalid syntax in interface config')) test_config_condition(not isinstance(port_ranges_conf, dict), ('Invalid syntax in interface ranges config')) port_num_to_port_conf = {} for port_key, port_conf in list(ports_conf.items()): test_config_condition(not isinstance(port_conf, dict), 'Invalid syntax in port config') if 'number' in port_conf: port_num = port_conf['number'] else: port_num = port_key try: port_num_to_port_conf[port_num] = (port_key, port_conf) except TypeError: raise InvalidConfigError('Invalid syntax in port config') for port_range, port_conf in list(port_ranges_conf.items()): # port range format: 1-6 OR 1-6,8-9 OR 1-3,5,7-9 test_config_condition(not isinstance(port_conf, dict), 'Invalid syntax in port config') port_nums = set() if 'number' in port_conf: del port_conf['number'] for range_ in re.findall(r'(\d+-\d+)', str(port_range)): start_num, end_num = [int(num) for num in range_.split('-')] test_config_condition(start_num >= end_num, ('Incorrect port range (%d - %d)' % (start_num, end_num))) port_nums.update(list(range(start_num, end_num + 1))) port_range = re.sub(range_, '', port_range) other_nums = [int(p) for p in re.findall(r'\d+', str(port_range))] port_nums.update(other_nums) test_config_condition(not port_nums, 'interface-ranges contain invalid config') for port_num in port_nums: if port_num in port_num_to_port_conf: # port range config has lower priority than individual port config for attr, value in list(port_conf.items()): port_num_to_port_conf[port_num][1].setdefault( attr, value) else: port_num_to_port_conf[port_num] = (port_num, port_conf) for port_num, port_conf in list(port_num_to_port_conf.values()): port = _dp_parse_port(dp_id, port_num, port_conf, vlans) dp.add_port(port) dp.reset_refs(vlans=vlans) for dp_key, dp_conf in list(dps_conf.items()): test_config_condition(not isinstance(dp_conf, dict), '') dp = DP(dp_key, dp_conf.get('dp_id', None), dp_conf) test_config_condition(dp.name != dp_key, ('DP key %s and DP name must match' % dp_key)) dp_id = dp.dp_id vlans = {} for vlan_key, vlan_conf in list(vlans_conf.items()): vlan = VLAN(vlan_key, dp_id, vlan_conf) vlans[vlan_key] = vlan test_config_condition( str(vlan_key) not in (str(vlan.vid), vlan.name), ('VLAN %s key must match VLAN name or VLAN VID' % vlan_key)) for acl_key, acl_conf in list(acls_conf.items()): acl = ACL(acl_key, dp_id, acl_conf) dp.add_acl(acl_key, acl) for router_key, router_conf in list(routers_conf.items()): router = Router(router_key, dp_id, router_conf) dp.add_router(router_key, router) for meter_key, meter_conf in list(meters_conf.items()): meter = Meter(meter_key, dp_id, meter_conf) dp.meters[meter_key] = meter _dp_add_ports(dp, dp_conf, dp_id, vlans) dps.append(dp) for dp in dps: dp.finalize_config(dps) for dp in dps: dp.resolve_stack_topology(dps) router_ref_dps = collections.defaultdict(set) for dp in dps: for router in list(dp.routers.keys()): router_ref_dps[router].add(dp) for router in list(routers_conf.keys()): test_config_condition( not router_ref_dps[router], ('router %s configured but not used by any DP' % router)) return dps
def resolve_acls(): """Resolve config references in ACLs.""" # TODO: move this config validation to ACL object. port_acl_matches = {} port_acl_set_fields = set() port_acl_exact_match = False port_acl_matches.update({'in_port': False}) port_acl_meter = False vlan_acl_matches = {} vlan_acl_exact_match = False vlan_acl_set_fields = set() vlan_acl_meter = False def merge_matches(matches, new_matches): for field, has_mask in list(new_matches.items()): if has_mask or field not in matches: matches[field] = has_mask for vlan in list(self.vlans.values()): if vlan.acls_in: acls = [] for acl in vlan.acls_in: matches, set_fields, meter = resolve_acl(acl, vlan.vid) merge_matches(vlan_acl_matches, matches) vlan_acl_set_fields = vlan_acl_set_fields.union( set_fields) if meter: vlan_acl_meter = True acls.append(self.acls[acl]) vlan.acls_in = acls vlan_acl_exact_match = verify_acl_exact_match(acls) for port in list(self.ports.values()): if port.acls_in: test_config_condition( self.dp_acls, ('dataplane ACLs cannot be used with port ACLs.')) acls = [] for acl in port.acls_in: matches, set_fields, meter = resolve_acl(acl, None) merge_matches(port_acl_matches, matches) port_acl_set_fields = port_acl_set_fields.union( set_fields) if meter: port_acl_meter = True acls.append(self.acls[acl]) port.acls_in = acls port_acl_exact_match = verify_acl_exact_match(acls) if self.dp_acls: acls = [] for acl in self.acls: matches, set_fields, meter = resolve_acl(acl, None) merge_matches(port_acl_matches, matches) port_acl_set_fields = port_acl_set_fields.union(set_fields) if meter: port_acl_meter = True acls.append(self.acls[acl]) self.dp_acls = acls port_acl_matches = { (field, mask) for field, mask in list(port_acl_matches.items()) } vlan_acl_matches = { (field, mask) for field, mask in list(vlan_acl_matches.items()) } # TODO: skip port_acl table if not configured. # TODO: dynamically configure output attribue override_table_config = { 'port_acl': ValveTableConfig('port_acl', exact_match=port_acl_exact_match, meter=port_acl_meter, output=True, match_types=port_acl_matches, set_fields=tuple(port_acl_set_fields)), 'vlan_acl': ValveTableConfig('vlan_acl', exact_match=vlan_acl_exact_match, meter=vlan_acl_meter, output=True, match_types=vlan_acl_matches, set_fields=tuple(vlan_acl_set_fields)), } return override_table_config
def check_config(self): super(VLAN, self).check_config() test_config_condition(not self.vid_valid(self.vid), 'invalid VID %s' % self.vid) test_config_condition(not netaddr.valid_mac(self.faucet_mac), ('invalid MAC address %s' % self.faucet_mac)) if self.max_hosts: if not self.proactive_arp_limit: self.proactive_arp_limit = 2 * self.max_hosts if not self.proactive_nd_limit: self.proactive_nd_limit = 2 * self.max_hosts if self.faucet_vips: try: self.faucet_vips = [ ipaddress.ip_interface(btos(ip)) for ip in self.faucet_vips ] except (ValueError, AttributeError, TypeError) as err: raise InvalidConfigError( 'Invalid IP address in faucet_vips: %s' % err) for faucet_vip in self.faucet_vips: self.dyn_faucet_vips_by_ipv[faucet_vip.version].append( faucet_vip) self.dyn_ipvs = list(self.dyn_faucet_vips_by_ipv.keys()) if self.bgp_neighbor_addresses or self.bgp_neighbour_addresses: neigh_addresses = set(self.bgp_neighbor_addresses + self.bgp_neighbour_addresses) try: self.bgp_neighbor_addresses = [ ipaddress.ip_address(btos(ip)) for ip in neigh_addresses ] except (ValueError, AttributeError, TypeError) as err: raise InvalidConfigError( 'Invalid IP address in bgp_neighbor_addresses: %s' % err) for bgp_neighbor_address in self.bgp_neighbor_addresses: self.dyn_bgp_neighbor_addresses_by_ipv[ bgp_neighbor_address.version].append(bgp_neighbor_address) if self.bgp_server_addresses: try: self.bgp_server_addresses = [ ipaddress.ip_address(btos(ip)) for ip in self.bgp_server_addresses ] except (ValueError, AttributeError, TypeError) as err: raise InvalidConfigError( 'Invalid IP address in bgp_server_addresses: %s' % err) for bgp_server_address in self.bgp_server_addresses: self.dyn_bgp_server_addresses_by_ipv[ bgp_server_address.version].append(bgp_server_address) self.dyn_bgp_ipvs = list( self.dyn_bgp_server_addresses_by_ipv.keys()) if self.bgp_as: test_config_condition(not isinstance(self.bgp_port, int), ('BGP port must be %s not %s' % (int, type(self.bgp_port)))) test_config_condition( self.bgp_connect_mode not in ('active', 'passive', 'both'), ('%s must be active, passive or both' % self.bgp_connect_mode)) test_config_condition( not ipaddress.IPv4Address(btos(self.bgp_routerid)), ('%s is not a valid IPv4 address' % (self.bgp_routerid))) test_config_condition(not self.bgp_neighbor_as, 'No BGP neighbor AS') test_config_condition(not self.bgp_neighbor_addresses, 'No BGP neighbor addresses') neighbor_ips = self.bgp_neighbor_addresses test_config_condition( len(neighbor_ips) != len(self.bgp_neighbor_addresses), ('Neighbor IPs is not the same length as BGP neighbor addresses' )) peer_versions = [ip.version for ip in neighbor_ips] test_config_condition( len(peer_versions) != 1 and self.bgp_connect_mode != 'active', ('if using multiple address families bgp_connect_mode must be active' )) if self.routes: try: self.routes = [route['route'] for route in self.routes] for route in self.routes: try: ip_gw = ipaddress.ip_address(btos(route['ip_gw'])) ip_dst = ipaddress.ip_network(btos(route['ip_dst'])) except (ValueError, AttributeError, TypeError) as err: raise InvalidConfigError( 'Invalid IP address in route: %s' % err) test_config_condition( ip_gw.version != ip_dst.version, 'ip_gw version does not match the ip_dst version') self.add_route(ip_dst, ip_gw) except KeyError as err: raise InvalidConfigError('missing route config %s' % err) except TypeError: raise InvalidConfigError('%s is not a valid routes value' % self.routes) test_config_condition( self.acl_in and self.acls_in, 'found both acl_in and acls_in, use only acls_in') if self.acl_in and not isinstance(self.acl_in, list): self.acls_in = [ self.acl_in, ] self.acl_in = None if self.acls_in: for acl in self.acls_in: test_config_condition(not isinstance(acl, (int, str)), 'acl names must be int or str')
def _dp_add_ports(dp, dp_conf, dp_id, vlans): ports_conf = dp_conf.get('interfaces', {}) port_ranges_conf = dp_conf.get('interface_ranges', {}) # as users can config port vlan by using vlan name, we store vid in # Port instance instead of vlan name for data consistency test_config_condition(not isinstance(ports_conf, dict), ('Invalid syntax in interface config')) test_config_condition(not isinstance(port_ranges_conf, dict), ('Invalid syntax in interface ranges config')) port_num_to_port_conf = {} for port_key, port_conf in list(ports_conf.items()): test_config_condition(not isinstance(port_conf, dict), 'Invalid syntax in port config') if 'number' in port_conf: port_num = port_conf['number'] else: port_num = port_key try: port_num_to_port_conf[port_num] = (port_key, port_conf) except TypeError: raise InvalidConfigError('Invalid syntax in port config') for port_range, port_conf in list(port_ranges_conf.items()): # port range format: 1-6 OR 1-6,8-9 OR 1-3,5,7-9 test_config_condition(not isinstance(port_conf, dict), 'Invalid syntax in port config') port_nums = set() if 'number' in port_conf: del port_conf['number'] for range_ in re.findall(r'(\d+-\d+)', str(port_range)): start_num, end_num = [int(num) for num in range_.split('-')] test_config_condition(start_num >= end_num, ('Incorrect port range (%d - %d)' % (start_num, end_num))) port_nums.update(list(range(start_num, end_num + 1))) port_range = re.sub(range_, '', port_range) other_nums = [int(p) for p in re.findall(r'\d+', str(port_range))] port_nums.update(other_nums) test_config_condition(not port_nums, 'interface-ranges contain invalid config') for port_num in port_nums: if port_num in port_num_to_port_conf: # port range config has lower priority than individual port config for attr, value in list(port_conf.items()): port_num_to_port_conf[port_num][1].setdefault( attr, value) else: port_num_to_port_conf[port_num] = (port_num, port_conf) for port_num, port_conf in list(port_num_to_port_conf.values()): port = _dp_parse_port(dp_id, port_num, port_conf, vlans) dp.add_port(port) dp.reset_refs(vlans=vlans)
def _resolve_output_ports(self, action_conf, resolve_port_cb, resolve_tunnel_objects): """Resolve the values for output actions in the ACL""" if isinstance(action_conf, (list, tuple)): return self._resolve_ordered_output_ports( action_conf, resolve_port_cb, resolve_tunnel_objects) result = {} test_config_condition( 'vlan_vid' in action_conf and 'vlan_vids' in action_conf, 'ACL %s has both vlan_vid and vlan_vids defined' % self._id) test_config_condition( 'port' in action_conf and 'ports' in action_conf, 'ACL %s has both port and ports defined' % self._id) for output_action, output_action_values in action_conf.items(): if output_action == 'tunnel': tunnel = output_action_values # Fetch tunnel items from the tunnel output dict test_config_condition( 'dp' not in tunnel, 'ACL (%s) tunnel DP not defined' % self._id) tunnel_dp = tunnel['dp'] tunnel_port = tunnel.get('port', None) tunnel_id = tunnel.get('tunnel_id', None) tunnel_type = tunnel.get('type', 'vlan') tunnel_exit_instructions = tunnel.get('exit_instructions', []) tunnel_direction = tunnel.get('bi_directional', False) tunnel_maintain = tunnel.get('maintain_encapsulation', False) tunnel_reverse = tunnel.get('reverse', False) test_config_condition( tunnel_reverse and tunnel_direction, ('Tunnel ACL %s cannot contain values for the fields' '`bi_directional` and `reverse` at the same time' % self._id)) # Resolve the tunnel items dst_dp, dst_port, tunnel_id = resolve_tunnel_objects( tunnel_dp, tunnel_port, tunnel_id) # Compile the tunnel into an easy-access dictionary tunnel_dict = { 'dst_dp': dst_dp, 'dst_port': dst_port, 'tunnel_id': tunnel_id, 'type': tunnel_type, 'exit_instructions': tunnel_exit_instructions, 'bi_directional': tunnel_direction, 'maintain_encapsulation': tunnel_maintain, 'reverse': tunnel_reverse, } self.tunnel_dests[tunnel_id] = tunnel_dict result[output_action] = tunnel_id elif output_action == 'port': port_name = output_action_values port = resolve_port_cb(port_name) test_config_condition( not port, ('ACL (%s) output port undefined in DP: %s'\ % (self._id, self.dp_id)) ) result[output_action] = port elif output_action == 'ports': resolved_ports = [ resolve_port_cb(p) for p in output_action_values] test_config_condition( None in resolved_ports, ('ACL (%s) output port(s) not defined in DP: %s'\ % (self._id, self.dp_id)) ) result[output_action] = resolved_ports elif output_action == 'failover': failover = output_action_values test_config_condition(not isinstance(failover, dict), ( 'failover is not a dictionary')) result[output_action] = {} for failover_name, failover_values in failover.items(): if failover_name == 'ports': resolved_ports = [ resolve_port_cb(p) for p in failover_values] test_config_condition( None in resolved_ports, ('ACL (%s) failover port(s) not defined in DP: %s'\ % (self._id, self.dp_id)) ) result[output_action][failover_name] = resolved_ports else: result[output_action][failover_name] = failover_values else: result[output_action] = output_action_values return result