Reintroduce "learn-action" firewall driver
This patch reintroduce the "learn-action" firewall driver for OVS. Change-Id: I821036ed31997a2fe44d39c55e7ccfe0cabdd22a
This commit is contained in:
parent
5086338a62
commit
26c06836bf
babel.cfg
doc/source/getstarted/devstack
networking_ovs_dpdk
openstack-common.confpuppet/ovsdpdk/manifests
requirements.txtsetup.cfgtest-requirements.txttox.ini
2
babel.cfg
Normal file
2
babel.cfg
Normal file
@ -0,0 +1,2 @@
|
||||
[python: **.py]
|
||||
|
@ -168,13 +168,20 @@ OVS-DPDK you will need to create a flavor that requests hugepages.
|
||||
|
||||
Enable the OVS firewall
|
||||
-----------------------
|
||||
To enable the OVS firewall, you will need to modify(or add) the following
|
||||
variable to local.conf:
|
||||
To enable the OVS firewall integrated in Neutron, you will need to modify
|
||||
(or add) the following variable to local.conf:
|
||||
|
||||
| [[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]]
|
||||
| [securitygroup]
|
||||
| firewall_driver = openvswitch
|
||||
|
||||
This is a stateful firewall and uses connection tracking to control the traffic
|
||||
flows. In this repository also can be found a stateless firewall based in
|
||||
OpenFlow 'learn action', which can be enabled by setting the following
|
||||
variable:
|
||||
|
||||
| firewall_driver = networking_ovs_dpdk.agent.ovs_dpdk_firewall.OVSFirewallDriver
|
||||
|
||||
By default, the multicast support is enabled. The default aging time for the
|
||||
IGMP subscriptions in the bridges is 3600 seconds. To configure the multicast
|
||||
support, both variables could be setup in local.conf:
|
||||
|
@ -194,16 +194,23 @@ OVS-DPDK you will need to create a flavor that requests hugepages.
|
||||
|
||||
Enable the OVS firewall
|
||||
-----------------------
|
||||
To enable the OVS firewall, you will need to modify (or add) the following
|
||||
variable to local.conf:
|
||||
To enable the OVS firewall integrated in Neutron, you will need to modify
|
||||
(or add) the following variable to local.conf:
|
||||
|
||||
| [[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]]
|
||||
| [securitygroup]
|
||||
| firewall_driver = openvswitch
|
||||
|
||||
This is a stateful firewall and uses connection tracking to control the traffic
|
||||
flows. In this repository also can be found a stateless firewall based in
|
||||
OpenFlow 'learn action', which can be enabled by setting the following
|
||||
variable:
|
||||
|
||||
| firewall_driver = networking_ovs_dpdk.agent.ovs_dpdk_firewall.OVSFirewallDriver
|
||||
|
||||
By default, the multicast support is enabled. The default aging time for the
|
||||
IGMP subscriptions in the bridges is 3600 seconds. To configure the multicast
|
||||
support, both variables can be setup in local.conf:
|
||||
support, both variables could be setup in local.conf:
|
||||
|
||||
| [[local|localrc]]
|
||||
| OVS_ENABLE_SG_FIREWALL_MULTICAST=[True/False]
|
||||
|
@ -155,13 +155,20 @@ create a flavor that requests hugepages.
|
||||
|
||||
Enable the OVS firewall
|
||||
-----------------------
|
||||
To enable the OVS firewall, you will need to modify (or add) the following
|
||||
variable to local.conf:
|
||||
To enable the OVS firewall integrated in Neutron, you will need to modify
|
||||
(or add) the following variable to local.conf:
|
||||
|
||||
| [[post-config|/etc/neutron/plugins/ml2/ml2_conf.ini]]
|
||||
| [securitygroup]
|
||||
| firewall_driver = openvswitch
|
||||
|
||||
This is a stateful firewall and uses connection tracking to control the traffic
|
||||
flows. In this repository also can be found a stateless firewall based in
|
||||
OpenFlow 'learn action', which can be enabled by setting the following
|
||||
variable:
|
||||
|
||||
| firewall_driver = networking_ovs_dpdk.agent.ovs_dpdk_firewall.OVSFirewallDriver
|
||||
|
||||
By default, the multicast support is enabled. The default aging time for the
|
||||
IGMP subscriptions in the bridges is 3600 seconds. To configure the multicast
|
||||
support, both variables could be setup in local.conf:
|
||||
|
19
networking_ovs_dpdk/__init__.py
Normal file
19
networking_ovs_dpdk/__init__.py
Normal file
@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import pbr.version
|
||||
|
||||
|
||||
__version__ = pbr.version.VersionInfo(
|
||||
'networking_ovs_dpdk').version_string()
|
0
networking_ovs_dpdk/agent/__init__.py
Normal file
0
networking_ovs_dpdk/agent/__init__.py
Normal file
990
networking_ovs_dpdk/agent/ovs_dpdk_firewall.py
Normal file
990
networking_ovs_dpdk/agent/ovs_dpdk_firewall.py
Normal file
@ -0,0 +1,990 @@
|
||||
# Copyright 2011 VMware, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
import netaddr
|
||||
import re
|
||||
|
||||
from neutron_lib import constants
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from networking_ovs_dpdk.common._i18n import _, _LW
|
||||
|
||||
from neutron.agent import firewall
|
||||
from neutron.common import utils as neutron_utils
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent.common import (constants
|
||||
as ovs_constants)
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent import \
|
||||
ovs_agent_extension_api
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
# OpenFlow Table IDs
|
||||
OF_ZERO_TABLE = ovs_constants.LOCAL_SWITCHING
|
||||
OF_SELECT_TABLE = ovs_constants.OVS_FIREWALL_TABLES[0]
|
||||
OF_EGRESS_TABLE = ovs_constants.OVS_FIREWALL_TABLES[1]
|
||||
OF_INGRESS_TABLE = ovs_constants.OVS_FIREWALL_TABLES[2]
|
||||
OF_INGRESS_EXT_TABLE = ovs_constants.OVS_FIREWALL_TABLES[3]
|
||||
|
||||
# Openflow ZERO table priorities
|
||||
OF_T0_ARP_INT_PRIO = 100
|
||||
OF_T0_ARP_EXT_PRIO = 90
|
||||
OF_T0_SELECT_TABLE_IN_PRIO = 50
|
||||
OF_T0_SELECT_TABLE_EXT_PRIO = 40
|
||||
OF_T0_BLOCK = 35
|
||||
|
||||
# Openflow SELECT table priorities
|
||||
OF_SEL_SERVICES_INT_IGMP_PRIO = 200
|
||||
OF_SEL_SERVICES_EXT_IGMP_PRIO = 190
|
||||
OF_SEL_SERVICES_EXT_MULTICAST_PRIO = 180
|
||||
OF_SEL_EGRESS_PRIO = 100
|
||||
OF_SEL_INGRESS_PRIO = 100
|
||||
OF_DROP_TRAFFIC_PRIO = 50
|
||||
|
||||
# Openflow EGRESS table priorities
|
||||
OF_EGRESS_SERVICES_PRIO = 50
|
||||
OF_EGRESS_ANTISPOOF_PRIO = 40
|
||||
OF_EGRESS_PORT_RULE_PRIO = 30
|
||||
OF_EGRESS_ALLOW_EGRESS_RULE_PRIO = 20
|
||||
|
||||
# Openflow INGRESS table priorities
|
||||
OF_INGRESS_SERVICES_PRIO = 50
|
||||
OF_INGRESS_ANTISPOOF_PRIO = 40
|
||||
OF_INGRESS_PORT_RULE_PRIO = 30
|
||||
OF_INGRESS_OUTBOUND_PRIO = 10
|
||||
|
||||
# Openflow INGRESS_EXT table priorities
|
||||
OF_INGRESS_EXT_BLOCK_INT_MAC_PRIO = 100
|
||||
OF_INGRESS_EXT_BLOCK_EXT_SOURCE_PRIO = 100
|
||||
OF_INGRESS_EXT_BLOCK_MULTICAST_PRIO = 100
|
||||
OF_INGRESS_EXT_ALLOW_EXT_TRAFFIC_PRIO = 50
|
||||
|
||||
# Openflow LEARN ACTIONS priorities
|
||||
OF_LEARNED_HIGH_PRIO = 100
|
||||
OF_LEARNED_LOW_PRIO = 90
|
||||
|
||||
INGRESS_DIRECTION = 'ingress'
|
||||
EGRESS_DIRECTION = 'egress'
|
||||
LEARN_IDLE_TIMEOUT = 30 # 30 seconds.
|
||||
LEARN_HARD_TIMEOUT = 1800 # 30 minutes.
|
||||
|
||||
# Ethernet type.
|
||||
IPv4 = "IPv4"
|
||||
IPv6 = "IPv6"
|
||||
|
||||
# OpenFlow mnemonics.
|
||||
OF_MNEMONICS = {
|
||||
IPv6: {
|
||||
"ip_dst": "ipv6_dst",
|
||||
"ip_src": "ipv6_src",
|
||||
"ip_proto": "ipv6",
|
||||
},
|
||||
IPv4: {
|
||||
"ip_dst": "nw_dst",
|
||||
"ip_src": "nw_src",
|
||||
"ip_proto": "ip",
|
||||
},
|
||||
}
|
||||
|
||||
# Protocols.
|
||||
IPV4_ROUTER_MESSAGES = [9, 10]
|
||||
# Router Solicitation (133)
|
||||
# Router Advertisement (134)
|
||||
# Neighbor Solicitation (135)
|
||||
# Neighbor Advertisement (136)
|
||||
# Redirect (137)
|
||||
ICMPV6_TYPE_RS = 133
|
||||
ICMPV6_TYPE_RA = constants.ICMPV6_TYPE_RA or 134
|
||||
ICMPv6_TYPE_NS = 135
|
||||
ICMPV6_TYPE_NA = constants.ICMPV6_TYPE_NA or 136
|
||||
ICMPV6_TYPE_RED = 137
|
||||
IPV6_ND_MESSAGES = [ICMPV6_TYPE_RS, ICMPV6_TYPE_RA, ICMPv6_TYPE_NS,
|
||||
ICMPV6_TYPE_NA, ICMPV6_TYPE_RED]
|
||||
# Multicast Listener Query (130)
|
||||
# Multicast Listener Report (131)
|
||||
# Multicast Listener Done (132)
|
||||
ICMPV6_TYPE_MLQ = 130
|
||||
ICMPV6_TYPE_MLR = 131
|
||||
ICMPV6_TYPE_MLD = 132
|
||||
IPV6_MLD_MESSAGES = [ICMPV6_TYPE_MLQ, ICMPV6_TYPE_MLR, ICMPV6_TYPE_MLD]
|
||||
|
||||
IPv6_MULTICAST_PREFIX = "ff02::1:ff00:0/104"
|
||||
|
||||
DIRECTION_IP_PREFIX = {'ingress': 'source_ip_prefix',
|
||||
'egress': 'dest_ip_prefix'}
|
||||
|
||||
ETH_PROTOCOL_TABLE = {IPv4: "0x0800",
|
||||
IPv6: "0x86dd"}
|
||||
|
||||
IP_PROTOCOL_TABLE = {
|
||||
constants.PROTO_NAME_TCP: constants.PROTO_NUM_TCP,
|
||||
constants.PROTO_NAME_ICMP: constants.PROTO_NUM_ICMP,
|
||||
constants.PROTO_NAME_IPV6_ICMP: constants.PROTO_NUM_IPV6_ICMP,
|
||||
constants.PROTO_NAME_UDP: constants.PROTO_NUM_UDP,
|
||||
"igmp": 2}
|
||||
|
||||
MULTICAST_MAC = "01:00:5e:00:00:00/01:00:5e:00:00:00"
|
||||
|
||||
ovs_opts = [
|
||||
cfg.BoolOpt('enable_sg_firewall_multicast', default=False,
|
||||
help=_("Allows multicast traffic coming into and going"
|
||||
"outside OVS.")),
|
||||
]
|
||||
cfg.CONF.register_opts(ovs_opts, "OVS")
|
||||
|
||||
|
||||
class OVSFirewallDriver(firewall.FirewallDriver):
|
||||
"""Driver which enforces security groups through
|
||||
Open vSwitch flows.
|
||||
"""
|
||||
|
||||
def __init__(self, integration_bridge):
|
||||
self._filtered_ports = {}
|
||||
self._int_br = ovs_agent_extension_api.\
|
||||
OVSCookieBridge(integration_bridge).deferred(full_ordered=True)
|
||||
self._deferred = False
|
||||
self._enable_multicast = \
|
||||
cfg.CONF.OVS.enable_sg_firewall_multicast
|
||||
|
||||
# List of security group rules for ports residing on this host
|
||||
self.sg_rules = {}
|
||||
|
||||
# List of security group member ips for ports residing on this
|
||||
# host
|
||||
self.sg_members = collections.defaultdict(
|
||||
lambda: collections.defaultdict(list))
|
||||
self.pre_sg_members = None
|
||||
|
||||
# Known ports managed.
|
||||
self._filtered_in_ports = {}
|
||||
|
||||
@property
|
||||
def ports(self):
|
||||
return self._filtered_ports
|
||||
|
||||
def _vif_port_info(self, port_name):
|
||||
"""Returns additional vif port info: internal vlan tag,
|
||||
interfaces, segmentation id, net id, network type, physical
|
||||
network.
|
||||
"""
|
||||
port_info = {'name': port_name}
|
||||
other_config = self._int_br.br.db_get_val("Port", port_name,
|
||||
"other_config")
|
||||
# Default fields (also other fields could be present):
|
||||
# net_uuid="e00e6a6a-c88a-4724-80a7-6368a94241d9"
|
||||
# network_type=vlan
|
||||
# physical_network=default
|
||||
# segmentation_id="1402"
|
||||
port_info.update(other_config)
|
||||
port_info['tag'] = self._int_br.br.db_get_val("Port", port_name, "tag")
|
||||
port_info['interfaces'] = \
|
||||
self._int_br.br.db_get_val('Port', port_name, 'interfaces')
|
||||
return port_info
|
||||
|
||||
def update_security_group_rules(self, sg_id, sg_rules):
|
||||
LOG.debug("Update rules of security group (%s)", sg_id)
|
||||
self.sg_rules[sg_id] = sg_rules
|
||||
|
||||
def update_security_group_members(self, sg_id, sg_members):
|
||||
LOG.debug("Update members of security group (%s)", sg_id)
|
||||
self.sg_members[sg_id] = collections.defaultdict(list, sg_members)
|
||||
|
||||
def security_group_updated(self, action_type, sec_group_ids,
|
||||
device_ids=[]):
|
||||
pass
|
||||
|
||||
def _expand_sg_rule_with_remote_ips(self, rule, port):
|
||||
"""Expand a remote group rule to rule per remote group IP."""
|
||||
remote_group_id = rule.get('remote_group_id')
|
||||
if remote_group_id:
|
||||
ethertype = rule['ethertype']
|
||||
port_ips = port.get('fixed_ips', [])
|
||||
|
||||
for ip in self.sg_members[remote_group_id][ethertype]:
|
||||
if ip not in port_ips:
|
||||
ip_rule = rule.copy()
|
||||
direction_ip_prefix = (
|
||||
DIRECTION_IP_PREFIX[rule['direction']])
|
||||
ip_prefix = str(netaddr.IPNetwork(ip).cidr)
|
||||
ip_rule[direction_ip_prefix] = ip_prefix
|
||||
yield ip_rule
|
||||
else:
|
||||
yield rule
|
||||
|
||||
def _write_proto(self, eth_type, protocol=None):
|
||||
if protocol == "arp":
|
||||
return "arp"
|
||||
|
||||
proto_str = "eth_type=%s" % ETH_PROTOCOL_TABLE[eth_type]
|
||||
if protocol in IP_PROTOCOL_TABLE.keys():
|
||||
proto_num = IP_PROTOCOL_TABLE[protocol]
|
||||
if protocol == constants.PROTO_NAME_ICMP and \
|
||||
eth_type == IPv6:
|
||||
proto_num = constants.PROTO_NUM_IPV6_ICMP
|
||||
proto_str += ",ip_proto=%s" % proto_num
|
||||
|
||||
return proto_str
|
||||
|
||||
def apply_port_filter(self, port):
|
||||
pass
|
||||
|
||||
def _add_flow(self, **kwargs):
|
||||
LOG.debug("OFW add rule: %s", kwargs)
|
||||
if self._deferred:
|
||||
self._int_br.add_flow(**kwargs)
|
||||
else:
|
||||
self._int_br.br.add_flow(**kwargs)
|
||||
|
||||
def _del_flows(self, **kwargs):
|
||||
LOG.debug("OFW del rule: %s", kwargs)
|
||||
if self._deferred:
|
||||
self._int_br.delete_flows(**kwargs)
|
||||
else:
|
||||
self._int_br.br.delete_flows(**kwargs)
|
||||
|
||||
def _add_base_flows(self, port, vif_port):
|
||||
"""Set base flows for every port."""
|
||||
self._add_zero_table(port, vif_port)
|
||||
self._add_selection_table(port, vif_port)
|
||||
self._add_selection_table_services(port, vif_port)
|
||||
self._add_selection_table_port_block(port)
|
||||
self._add_egress_antispoof(port, vif_port)
|
||||
self._add_egress_services(port, vif_port)
|
||||
self._add_ingress_allow_outbound_traffic(port)
|
||||
self._add_ingress_services(port, vif_port)
|
||||
|
||||
def _add_zero_table(self, port, vif_port):
|
||||
"""Set arp flows. The rest of the traffic is sent to
|
||||
SELECT_TABLE.
|
||||
"""
|
||||
segmentation_id = port['vinfo']['segmentation_id']
|
||||
# ARP and ND traffic to be delivered to an internal port.
|
||||
for fixed_ip in port['fixed_ips']:
|
||||
# IPv4, ARP.
|
||||
if self._ip_version_from_address(fixed_ip) == IPv4:
|
||||
self._add_flow(priority=OF_T0_ARP_INT_PRIO,
|
||||
table=OF_ZERO_TABLE,
|
||||
proto=self._write_proto(IPv4, "arp"),
|
||||
dl_vlan=segmentation_id,
|
||||
nw_dst=fixed_ip,
|
||||
actions='strip_vlan,output:%s'
|
||||
% vif_port.ofport)
|
||||
|
||||
# IPv6, NS and NA.
|
||||
if self._ip_version_from_address(fixed_ip) == IPv6:
|
||||
for icmpv6_type in IPV6_ND_MESSAGES:
|
||||
self._add_flow(priority=OF_T0_ARP_INT_PRIO,
|
||||
table=OF_ZERO_TABLE,
|
||||
proto=self._write_proto(IPv6,
|
||||
constants.PROTO_NAME_IPV6_ICMP),
|
||||
dl_vlan=segmentation_id,
|
||||
icmpv6_type=icmpv6_type,
|
||||
ipv6_dst=fixed_ip,
|
||||
actions='strip_vlan,output:%s'
|
||||
% vif_port.ofport)
|
||||
|
||||
# Internal port ARP messages to be delivered out of br-int.
|
||||
self._add_flow(priority=OF_T0_ARP_EXT_PRIO,
|
||||
table=OF_ZERO_TABLE,
|
||||
proto='arp',
|
||||
actions='normal')
|
||||
|
||||
# Internal port NS and NA messages to be delivered out of br-int.
|
||||
for icmpv6_type in IPV6_ND_MESSAGES:
|
||||
self._add_flow(priority=OF_T0_ARP_EXT_PRIO,
|
||||
table=OF_ZERO_TABLE,
|
||||
proto=self._write_proto(IPv6,
|
||||
constants.PROTO_NAME_IPV6_ICMP),
|
||||
icmpv6_type=icmpv6_type,
|
||||
actions='normal')
|
||||
|
||||
# Select traffic.
|
||||
# Incoming internal traffic: check mac, mod vlan.
|
||||
self._add_flow(priority=OF_T0_SELECT_TABLE_IN_PRIO,
|
||||
table=OF_ZERO_TABLE,
|
||||
dl_src=port['mac_address'],
|
||||
actions="mod_vlan_vid:%s,"
|
||||
"load:0->NXM_NX_REG0[0..11],"
|
||||
"load:0->NXM_NX_REG1[0..11],"
|
||||
"resubmit(,%s)"
|
||||
% (port['vinfo']['tag'], OF_SELECT_TABLE))
|
||||
|
||||
# Incoming external traffic: check external vlan tag, mod vlan.
|
||||
self._add_flow(priority=OF_T0_SELECT_TABLE_EXT_PRIO,
|
||||
table=OF_ZERO_TABLE,
|
||||
dl_vlan=segmentation_id,
|
||||
actions="mod_vlan_vid:%s,"
|
||||
"load:%s->NXM_NX_REG0[0..11],"
|
||||
"load:0->NXM_NX_REG1[0..11],"
|
||||
"resubmit(,%s)"
|
||||
% (port['vinfo']['tag'],
|
||||
port['vinfo']['tag'],
|
||||
OF_SELECT_TABLE))
|
||||
|
||||
# Block rest of traffic.
|
||||
self._add_flow(priority=OF_T0_BLOCK,
|
||||
table=OF_ZERO_TABLE,
|
||||
actions="drop")
|
||||
|
||||
def _add_selection_table(self, port, vif_port):
|
||||
"""Set traffic selection basic rules.
|
||||
Allows all internal traffic matching mac/ip to egress table.
|
||||
Allows all extenal traffic matching dst mac to ingress table.
|
||||
"""
|
||||
for fixed_ip in port['fixed_ips']:
|
||||
if self._ip_version_from_address(fixed_ip) == IPv4:
|
||||
self._add_flow(priority=OF_SEL_EGRESS_PRIO,
|
||||
table=OF_SELECT_TABLE,
|
||||
in_port=vif_port.ofport,
|
||||
proto=self._write_proto(IPv4),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_src=port['mac_address'],
|
||||
nw_src=fixed_ip,
|
||||
actions='resubmit(,%s)' %
|
||||
(OF_EGRESS_TABLE))
|
||||
|
||||
if self._ip_version_from_address(fixed_ip) == IPv6:
|
||||
self._add_flow(priority=OF_SEL_EGRESS_PRIO,
|
||||
table=OF_SELECT_TABLE,
|
||||
in_port=vif_port.ofport,
|
||||
proto=self._write_proto(IPv6),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_src=port['mac_address'],
|
||||
ipv6_src=fixed_ip,
|
||||
actions='resubmit(,%s)' %
|
||||
(OF_EGRESS_TABLE))
|
||||
|
||||
# External traffic to ingress processing table
|
||||
self._add_flow(
|
||||
priority=OF_SEL_INGRESS_PRIO,
|
||||
table=OF_SELECT_TABLE,
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_dst=port['mac_address'],
|
||||
actions='resubmit(,%d)'
|
||||
% (OF_INGRESS_TABLE))
|
||||
|
||||
def _add_selection_table_services(self, port, vif_port):
|
||||
"""Selection table services:
|
||||
Allows DHCP traffic to request an IP address
|
||||
IGMP snooping/MLD traffic and multicast traffic.
|
||||
"""
|
||||
# Allow DHCP requests from invalid address
|
||||
self._add_flow(priority=OF_SEL_EGRESS_PRIO,
|
||||
table=OF_SELECT_TABLE,
|
||||
in_port=vif_port.ofport,
|
||||
proto=self._write_proto(IPv4),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_src=port['mac_address'],
|
||||
nw_src='0.0.0.0',
|
||||
actions="resubmit(,%s)"
|
||||
% (OF_EGRESS_TABLE))
|
||||
|
||||
if self._enable_multicast:
|
||||
for fixed_ip in port['fixed_ips']:
|
||||
if self._ip_version_from_address(fixed_ip) == IPv4:
|
||||
# Add internal IGMP snooping traffic support. This traffic
|
||||
# is sent to the bridge using the 'normal' action.
|
||||
self._add_flow(priority=OF_SEL_SERVICES_INT_IGMP_PRIO,
|
||||
table=OF_SELECT_TABLE,
|
||||
in_port=vif_port.ofport,
|
||||
proto=self._write_proto(IPv4, "igmp"),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_src=port['mac_address'],
|
||||
dl_dst=MULTICAST_MAC,
|
||||
nw_src=fixed_ip,
|
||||
nw_dst=str(netaddr.ip.IPV4_MULTICAST.cidr),
|
||||
actions='strip_vlan,normal')
|
||||
|
||||
if self._ip_version_from_address(fixed_ip) == IPv6:
|
||||
# Add internal MLD snooping traffic support.
|
||||
self._add_flow(priority=OF_SEL_SERVICES_INT_IGMP_PRIO,
|
||||
table=OF_SELECT_TABLE,
|
||||
in_port=vif_port.ofport,
|
||||
proto=self._write_proto(IPv6,
|
||||
constants.PROTO_NAME_IPV6_ICMP),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_src=port['mac_address'],
|
||||
dl_dst=MULTICAST_MAC,
|
||||
ipv6_src=fixed_ip,
|
||||
ipv6_dst=str(netaddr.ip.IPV6_MULTICAST.cidr),
|
||||
actions='strip_vlan,normal')
|
||||
|
||||
# Add external IGMP snooping traffic support.
|
||||
self._add_flow(priority=OF_SEL_SERVICES_EXT_IGMP_PRIO,
|
||||
table=OF_SELECT_TABLE,
|
||||
proto=self._write_proto(IPv4, "igmp"),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_dst=MULTICAST_MAC,
|
||||
nw_dst=str(netaddr.ip.IPV4_MULTICAST.cidr),
|
||||
actions='normal')
|
||||
|
||||
# Add external MLD snooping traffic support.
|
||||
self._add_flow(priority=OF_SEL_SERVICES_EXT_IGMP_PRIO,
|
||||
table=OF_SELECT_TABLE,
|
||||
proto=self._write_proto(IPv6,
|
||||
constants.PROTO_NAME_IPV6_ICMP),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_dst=MULTICAST_MAC,
|
||||
ipv6_dst=str(netaddr.ip.IPV6_MULTICAST.cidr),
|
||||
actions='normal')
|
||||
|
||||
# Allow external multicast traffic to skip the internal MAC filter.
|
||||
# This traffic is sent to the ingress table. Only TCP and UDP.
|
||||
# REG0 in external traffic must match the internal VLAN tag.
|
||||
for proto in [constants.PROTO_NAME_TCP,
|
||||
constants.PROTO_NAME_UDP]:
|
||||
self._add_flow(priority=OF_SEL_SERVICES_EXT_MULTICAST_PRIO,
|
||||
table=OF_SELECT_TABLE,
|
||||
reg0="%s" % port['vinfo']['tag'],
|
||||
proto=self._write_proto(IPv4, proto),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_dst=MULTICAST_MAC,
|
||||
nw_dst=str(netaddr.ip.IPV4_MULTICAST.cidr),
|
||||
actions='load:1->NXM_NX_REG1[0..11],'
|
||||
'resubmit(,%s)' % OF_INGRESS_TABLE)
|
||||
self._add_flow(priority=OF_SEL_SERVICES_EXT_MULTICAST_PRIO,
|
||||
table=OF_SELECT_TABLE,
|
||||
reg0="%s" % port['vinfo']['tag'],
|
||||
proto=self._write_proto(IPv6, proto),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_dst=MULTICAST_MAC,
|
||||
ipv6_dst=str(netaddr.ip.IPV6_MULTICAST.cidr),
|
||||
actions='load:1->NXM_NX_REG1[0..11],'
|
||||
'resubmit(,%s)' % OF_INGRESS_TABLE)
|
||||
|
||||
def _add_selection_table_port_block(self, port):
|
||||
"""Block rest of the traffic.
|
||||
Drop all traffic not generated by or to a VM.
|
||||
"""
|
||||
for eth_type in ETH_PROTOCOL_TABLE.keys():
|
||||
self._add_flow(
|
||||
priority=OF_DROP_TRAFFIC_PRIO,
|
||||
table=OF_SELECT_TABLE,
|
||||
proto=self._write_proto(eth_type),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
actions='drop')
|
||||
|
||||
def _add_egress_antispoof(self, port, vif_port):
|
||||
"""Set antispoof rules.
|
||||
Antispoof rules take precedence to any rules set by
|
||||
the tenant in the security group.
|
||||
"""
|
||||
# No DHCPv4 server out from port.
|
||||
self._add_flow(priority=OF_EGRESS_ANTISPOOF_PRIO,
|
||||
table=OF_EGRESS_TABLE,
|
||||
in_port=vif_port.ofport,
|
||||
proto=self._write_proto(IPv4,
|
||||
constants.PROTO_NAME_UDP),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
udp_src=67,
|
||||
udp_dst=68,
|
||||
actions='drop')
|
||||
|
||||
# No DHCPv6 server out from port.
|
||||
self._add_flow(priority=OF_EGRESS_ANTISPOOF_PRIO,
|
||||
table=OF_EGRESS_TABLE,
|
||||
in_port=vif_port.ofport,
|
||||
proto=self._write_proto(IPv6,
|
||||
constants.PROTO_NAME_UDP),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
udp_src=547,
|
||||
udp_dst=546,
|
||||
actions='drop')
|
||||
|
||||
def _add_egress_services(self, port, vif_port):
|
||||
"""Add service rules.
|
||||
Allows traffic to DHCPv4/v6 servers.
|
||||
Allows icmp traffic.
|
||||
"""
|
||||
# DHCP & DHCPv6.
|
||||
for eth_type, udp_src, udp_dst in [(IPv4, 68, 67), (IPv6, 546, 547)]:
|
||||
self._add_flow(
|
||||
table=OF_EGRESS_TABLE,
|
||||
priority=OF_EGRESS_SERVICES_PRIO,
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_src=port['mac_address'],
|
||||
in_port=vif_port.ofport,
|
||||
proto=self._write_proto(eth_type, constants.PROTO_NAME_UDP),
|
||||
udp_src=udp_src,
|
||||
udp_dst=udp_dst,
|
||||
actions='resubmit(,%s)' % OF_INGRESS_TABLE)
|
||||
|
||||
# Allows ICMP router advertisement / router selection.
|
||||
for type in IPV4_ROUTER_MESSAGES:
|
||||
self._add_flow(
|
||||
table=OF_EGRESS_TABLE,
|
||||
priority=OF_EGRESS_SERVICES_PRIO,
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_src=port['mac_address'],
|
||||
proto=self._write_proto(IPv4,
|
||||
constants.PROTO_NAME_ICMP),
|
||||
icmp_type=type,
|
||||
actions='resubmit(,%s)' % OF_INGRESS_TABLE)
|
||||
|
||||
# Allows IPv6 MLD messages.
|
||||
for icmpv6_type in IPV6_MLD_MESSAGES:
|
||||
self._add_flow(
|
||||
table=OF_EGRESS_TABLE,
|
||||
priority=OF_EGRESS_SERVICES_PRIO,
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_src=port['mac_address'],
|
||||
in_port=vif_port.ofport,
|
||||
proto=self._write_proto(IPv6,
|
||||
constants.PROTO_NAME_IPV6_ICMP),
|
||||
icmpv6_type=icmpv6_type,
|
||||
actions='resubmit(,%s)' % OF_INGRESS_TABLE)
|
||||
|
||||
def _add_ingress_allow_outbound_traffic(self, port):
|
||||
"""Allows ingress outbound traffic.
|
||||
By default, all ingress traffic not matching any internal port
|
||||
in the bridge is sent to the external ports.
|
||||
"""
|
||||
# If the traffic do not match any mac address inside the bridge,
|
||||
# send the traffic to the INGRESS_EXT table.
|
||||
self._add_flow(
|
||||
table=OF_INGRESS_TABLE,
|
||||
priority=OF_INGRESS_OUTBOUND_PRIO,
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
actions='resubmit(,%s)' % OF_INGRESS_EXT_TABLE)
|
||||
|
||||
# Blocks all traffic in this table if it's for an internal
|
||||
# port (mac address).
|
||||
self._add_flow(
|
||||
table=OF_INGRESS_EXT_TABLE,
|
||||
priority=OF_INGRESS_EXT_BLOCK_INT_MAC_PRIO,
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_dst=port['mac_address'],
|
||||
actions='drop')
|
||||
|
||||
# Blocks all traffic in this table if was sent by an external
|
||||
# source. Prevents from sending back the traffic.
|
||||
self._add_flow(
|
||||
table=OF_INGRESS_EXT_TABLE,
|
||||
priority=OF_INGRESS_EXT_BLOCK_EXT_SOURCE_PRIO,
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
reg0="%s" % port['vinfo']['tag'],
|
||||
actions='drop')
|
||||
|
||||
# Use normal action to send the traffic outside the integration
|
||||
# bridge.
|
||||
self._add_flow(
|
||||
table=OF_INGRESS_EXT_TABLE,
|
||||
priority=OF_INGRESS_EXT_ALLOW_EXT_TRAFFIC_PRIO,
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
actions='strip_vlan,normal')
|
||||
|
||||
def _add_ingress_services(self, port, vif_port):
|
||||
"""Add service rules.dl_vlan=port['vinfo']['tag'],
|
||||
Allows traffic to DHCPv4/v6 servers
|
||||
Allows specific icmp traffic (RA messages).
|
||||
"""
|
||||
# DHCP & DHCPv6.
|
||||
for eth_type, udp_src, udp_dst in [(IPv4, 67, 68), (IPv6, 547, 546)]:
|
||||
self._add_flow(
|
||||
table=OF_INGRESS_TABLE,
|
||||
priority=OF_INGRESS_SERVICES_PRIO,
|
||||
proto=self._write_proto(eth_type, constants.PROTO_NAME_UDP),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_dst=port['mac_address'],
|
||||
udp_src=udp_src,
|
||||
udp_dst=udp_dst,
|
||||
actions=self._get_ingress_actions(vif_port))
|
||||
|
||||
# ICMP RA messages.
|
||||
for type in [9, 10]:
|
||||
self._add_flow(
|
||||
table=OF_INGRESS_TABLE,
|
||||
priority=OF_INGRESS_SERVICES_PRIO,
|
||||
proto=self._write_proto(IPv4,
|
||||
constants.PROTO_NAME_ICMP),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_dst=port['mac_address'],
|
||||
icmp_type=type,
|
||||
actions=self._get_ingress_actions(vif_port))
|
||||
|
||||
# ICMP6 MLD messages.
|
||||
for icmpv6_type in IPV6_MLD_MESSAGES:
|
||||
self._add_flow(
|
||||
table=OF_INGRESS_TABLE,
|
||||
priority=OF_INGRESS_SERVICES_PRIO,
|
||||
proto=self._write_proto(IPv6,
|
||||
constants.PROTO_NAME_IPV6_ICMP),
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
dl_dst=port['mac_address'],
|
||||
icmpv6_type=icmpv6_type,
|
||||
actions=self._get_ingress_actions(vif_port))
|
||||
|
||||
if self._enable_multicast:
|
||||
# Block the multicast traffic in the external ingress table.
|
||||
self._add_flow(
|
||||
table=OF_INGRESS_EXT_TABLE,
|
||||
priority=OF_INGRESS_EXT_BLOCK_MULTICAST_PRIO,
|
||||
dl_vlan=port['vinfo']['tag'],
|
||||
reg1="1",
|
||||
actions='drop')
|
||||
|
||||
def _get_ingress_actions(self, vif_port):
|
||||
return 'strip_vlan,output:%(oport)s' % \
|
||||
{'oport': vif_port.ofport}
|
||||
|
||||
def _remove_flows(self, port):
|
||||
# Remove manual and "learn action" rules.
|
||||
self._del_flows(dl_src=port["mac_address"])
|
||||
self._del_flows(dl_dst=port["mac_address"])
|
||||
# Remove antispoof rules.
|
||||
if self._filtered_in_ports.get(port['device']):
|
||||
self._del_flows(in_port=self._filtered_in_ports.get(
|
||||
port['device']))
|
||||
# Remove ARP rules.
|
||||
for ipv4 in [ip for ip in port['fixed_ips'] if
|
||||
self._ip_version_from_address(ip) == IPv4]:
|
||||
self._del_flows(table=OF_ZERO_TABLE, nw_dst=ipv4,
|
||||
proto=self._write_proto(IPv4, "arp"))
|
||||
# Remove ND rules.
|
||||
for ipv6 in [ip for ip in port['fixed_ips'] if
|
||||
self._ip_version_from_address(ip) == IPv6]:
|
||||
self._del_flows(table=OF_ZERO_TABLE, ipv6_dst=ipv6,
|
||||
proto=self._write_proto(IPv6, constants.PROTO_NAME_IPV6_ICMP))
|
||||
|
||||
def _write_multicast_flow(self, flow, direction, port, port_match,
|
||||
priority, ip_version):
|
||||
"""Write a flow for the manual rule, allowing multicast traffic.
|
||||
"""
|
||||
# Multicast ingress traffic.
|
||||
# Check the traffic protocol: only tcp or udp. Also check the
|
||||
# multicast destination MAC and IP.
|
||||
if self._enable_multicast \
|
||||
and direction == INGRESS_DIRECTION \
|
||||
and flow['proto'] in [constants.PROTO_NAME_TCP,
|
||||
constants.PROTO_NAME_UDP]:
|
||||
hp_flow = dict.copy(flow)
|
||||
ip_dst = str(netaddr.ip.IPV6_MULTICAST.cidr) \
|
||||
if ip_version == IPv6 \
|
||||
else str(netaddr.ip.IPV4_MULTICAST.cidr)
|
||||
hp_flow[OF_MNEMONICS[ip_version]['ip_dst']] = ip_dst
|
||||
hp_flow['dl_vlan'] = port['vinfo']['tag']
|
||||
hp_flow['priority'] = priority
|
||||
hp_flow['dl_dst'] = MULTICAST_MAC
|
||||
hp_flow['table'] = OF_INGRESS_TABLE
|
||||
hp_flow['actions'] = \
|
||||
"move:NXM_NX_REG0[0..11]->NXM_OF_VLAN_TCI[0..11],normal"
|
||||
self._write_flows_per_port_match(hp_flow, port_match)
|
||||
|
||||
def _get_learn_action_rule(self, direction, priority,
|
||||
port_range_min, port_range_max,
|
||||
eth_type, ip_proto, vif_port):
|
||||
# Write protocol string.
|
||||
proto_str = self._write_proto(eth_type, ip_proto)
|
||||
port_dst_str = ""
|
||||
port_src_str = ""
|
||||
if ip_proto in [constants.PROTO_NAME_TCP,
|
||||
constants.PROTO_NAME_UDP]:
|
||||
# Known L4 protocols with configurable ports.
|
||||
port_dst_str = \
|
||||
"NXM_OF_%(ip_proto)s_DST[]=NXM_OF_%(ip_proto)s_SRC[]," \
|
||||
% {'ip_proto': ip_proto.upper()}
|
||||
port_src_str = \
|
||||
"NXM_OF_%(ip_proto)s_SRC[]=NXM_OF_%(ip_proto)s_DST[]," \
|
||||
% {'ip_proto': ip_proto.upper()}
|
||||
|
||||
# Setup ICMPv4/v6 type and code.
|
||||
icmp_type = ""
|
||||
icmp_code = ""
|
||||
if ip_proto == constants.PROTO_NAME_ICMP:
|
||||
if port_range_min == 8:
|
||||
icmp_type = "icmp_type=%s," % 0
|
||||
elif port_range_min == 13:
|
||||
icmp_type = "icmp_type=%s," % 14
|
||||
elif port_range_min == 15:
|
||||
icmp_type = "icmp_type=%s," % 16
|
||||
elif port_range_min == 17:
|
||||
icmp_type = "icmp_type=%s," % 18
|
||||
elif port_range_min:
|
||||
icmp_type = "icmp_type=%s," % port_range_min
|
||||
|
||||
if port_range_max:
|
||||
icmp_code = "icmp_code=%s," % port_range_max
|
||||
elif ip_proto == constants.PROTO_NAME_IPV6_ICMP:
|
||||
if port_range_min:
|
||||
icmp_type = "icmpv6_type=%s," % port_range_min
|
||||
if port_range_max:
|
||||
icmp_code = "icmpv6_code=%s," % port_range_max
|
||||
|
||||
# Source and destination IPs.
|
||||
if eth_type == IPv4:
|
||||
ip_dst = "NXM_OF_IP_DST[]=NXM_OF_IP_SRC[],"
|
||||
ip_src = "NXM_OF_IP_SRC[]=NXM_OF_IP_DST[],"
|
||||
else:
|
||||
ip_dst = "NXM_NX_IPV6_DST[]=NXM_NX_IPV6_SRC[],"
|
||||
ip_src = "NXM_NX_IPV6_SRC[]=NXM_NX_IPV6_DST[],"
|
||||
|
||||
# Learn action store table:
|
||||
if direction == INGRESS_DIRECTION:
|
||||
learn_action_table = OF_EGRESS_TABLE
|
||||
elif direction == EGRESS_DIRECTION:
|
||||
learn_action_table = OF_INGRESS_TABLE
|
||||
|
||||
learn_actions = "learn(table=%(table)s," \
|
||||
"priority=%(priority)s," \
|
||||
"idle_timeout=%(idle_timeout)s," \
|
||||
"hard_timeout=%(hard_timeout)s," \
|
||||
"%(proto)s," \
|
||||
"NXM_OF_ETH_SRC[]=NXM_OF_ETH_DST[]," \
|
||||
"NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[]," \
|
||||
"%(ip_src)s" \
|
||||
"%(ip_dst)s" \
|
||||
"%(port_dst)s" \
|
||||
"%(port_src)s" \
|
||||
"%(icmp_type)s" \
|
||||
"%(icmp_code)s" \
|
||||
"NXM_OF_VLAN_TCI[0..11]," \
|
||||
"load:NXM_NX_REG0[0..11]->NXM_OF_VLAN_TCI[0..11]," \
|
||||
"output:NXM_OF_IN_PORT[])" % \
|
||||
{'table': learn_action_table,
|
||||
'priority': priority,
|
||||
'idle_timeout': LEARN_IDLE_TIMEOUT,
|
||||
'hard_timeout': LEARN_HARD_TIMEOUT,
|
||||
'proto': proto_str,
|
||||
'ip_src': ip_src,
|
||||
'ip_dst': ip_dst,
|
||||
'port_dst': port_dst_str,
|
||||
'port_src': port_src_str,
|
||||
'icmp_type': icmp_type,
|
||||
'icmp_code': icmp_code}
|
||||
|
||||
if direction == EGRESS_DIRECTION:
|
||||
return "%(learn_actions)s," \
|
||||
"resubmit(,%(table)s)" % \
|
||||
{'learn_actions': learn_actions,
|
||||
'table': OF_INGRESS_TABLE}
|
||||
elif direction == INGRESS_DIRECTION:
|
||||
return "%(learn_actions)s," \
|
||||
"strip_vlan,output:%(ofport)s" % \
|
||||
{'learn_actions': learn_actions,
|
||||
'ofport': vif_port.ofport}
|
||||
|
||||
def _select_sg_rules_for_port(self, port):
|
||||
"""Select rules from the security groups the port is member of."""
|
||||
port_sg_ids = port.get('security_groups', [])
|
||||
port_rules = []
|
||||
|
||||
for sg_id in port_sg_ids:
|
||||
for rule in self.sg_rules.get(sg_id, []):
|
||||
port_rules.extend(
|
||||
self._expand_sg_rule_with_remote_ips(rule, port))
|
||||
return port_rules
|
||||
|
||||
def _write_flows_per_port_match(self, flow, port_match):
|
||||
if port_match == "" or isinstance(flow[port_match], int):
|
||||
self._add_flow(**flow)
|
||||
elif isinstance(flow[port_match], list):
|
||||
for portm in flow[port_match]:
|
||||
hp_flow = dict.copy(flow)
|
||||
hp_flow[port_match] = portm
|
||||
self._add_flow(**hp_flow)
|
||||
|
||||
def _write_flows_per_ip(self, flow, rule, port, port_match, ip_proto):
|
||||
"""Write the needed flows per each IP in the port."""
|
||||
vif_port = self._int_br.br.get_vif_port_by_id(port['device'])
|
||||
if not vif_port:
|
||||
LOG.warning(_LW("Port %(port_id)s not present in bridge. Skip "
|
||||
"applying rules for this port"),
|
||||
{'port_id': port})
|
||||
return
|
||||
|
||||
# Write a rule(s) per ip.
|
||||
for fixed_ip in port['fixed_ips']:
|
||||
# Check if the rule and the IP address have the same version.
|
||||
if rule['ethertype'] != \
|
||||
self._ip_version_from_address(fixed_ip):
|
||||
continue
|
||||
|
||||
if rule['direction'] == EGRESS_DIRECTION:
|
||||
flow[OF_MNEMONICS[rule['ethertype']]['ip_src']] = fixed_ip
|
||||
elif rule['direction'] == INGRESS_DIRECTION:
|
||||
flow[OF_MNEMONICS[rule['ethertype']]['ip_dst']] = fixed_ip
|
||||
|
||||
# Write learn actions.
|
||||
# Default protocol: "ip". Create high priority rules
|
||||
# for TCP and UDP protocols.
|
||||
if not ip_proto:
|
||||
for proto in [constants.PROTO_NAME_TCP,
|
||||
constants.PROTO_NAME_UDP]:
|
||||
hp_flow = dict.copy(flow)
|
||||
hp_flow['proto'] = self._write_proto(rule['ethertype'],
|
||||
proto)
|
||||
hp_flow['actions'] = self._get_learn_action_rule(
|
||||
rule['direction'],
|
||||
OF_LEARNED_HIGH_PRIO,
|
||||
"",
|
||||
"",
|
||||
rule['ethertype'],
|
||||
proto,
|
||||
vif_port)
|
||||
self._write_flows_per_port_match(hp_flow, port_match)
|
||||
|
||||
# Write normal "learn action" for every flow.
|
||||
flow['actions'] = self._get_learn_action_rule(
|
||||
rule['direction'],
|
||||
OF_LEARNED_LOW_PRIO,
|
||||
rule.get('port_range_min'),
|
||||
rule.get('port_range_max'),
|
||||
rule['ethertype'],
|
||||
rule.get('protocol'),
|
||||
vif_port)
|
||||
self._write_flows_per_port_match(flow, port_match)
|
||||
|
||||
# Write multicast rule.
|
||||
self._write_multicast_flow(flow, rule['direction'], port,
|
||||
port_match, OF_LEARNED_LOW_PRIO,
|
||||
rule['ethertype'])
|
||||
|
||||
def _add_rules_flows(self, port):
|
||||
rules = self._select_sg_rules_for_port(port)
|
||||
for rule in rules:
|
||||
ethertype = rule['ethertype']
|
||||
direction = rule['direction']
|
||||
protocol = rule.get('protocol')
|
||||
port_range_min = rule.get('port_range_min')
|
||||
port_range_max = rule.get('port_range_max')
|
||||
source_ip_prefix = rule.get('source_ip_prefix')
|
||||
dest_ip_prefix = rule.get('dest_ip_prefix')
|
||||
|
||||
flow = {}
|
||||
# Direcction.
|
||||
if direction == EGRESS_DIRECTION:
|
||||
flow['priority'] = OF_EGRESS_PORT_RULE_PRIO
|
||||
flow['table'] = OF_EGRESS_TABLE
|
||||
flow["dl_src"] = port["mac_address"]
|
||||
|
||||
elif direction == INGRESS_DIRECTION:
|
||||
flow['priority'] = OF_INGRESS_PORT_RULE_PRIO
|
||||
flow['table'] = OF_INGRESS_TABLE
|
||||
flow["dl_dst"] = port["mac_address"]
|
||||
|
||||
# Protocol.
|
||||
flow['proto'] = self._write_proto(ethertype, protocol)
|
||||
|
||||
# Port range.
|
||||
port_match = ""
|
||||
if (port_range_min and port_range_max and
|
||||
protocol in [constants.PROTO_NAME_TCP,
|
||||
constants.PROTO_NAME_UDP]):
|
||||
port_match = "%s_dst" % protocol
|
||||
if port_range_max > port_range_min:
|
||||
flow[port_match] = neutron_utils.port_rule_masking(
|
||||
port_range_min,
|
||||
port_range_max)
|
||||
else:
|
||||
flow[port_match] = int(port_range_min)
|
||||
|
||||
# Destination and source address.
|
||||
if dest_ip_prefix and dest_ip_prefix != "0.0.0.0/0":
|
||||
flow[OF_MNEMONICS[ethertype]["ip_dst"]] = dest_ip_prefix
|
||||
|
||||
if source_ip_prefix and source_ip_prefix != "0.0.0.0/0":
|
||||
flow[OF_MNEMONICS[ethertype]["ip_src"]] = source_ip_prefix
|
||||
|
||||
# Write flow.
|
||||
self._write_flows_per_ip(flow, rule, port, port_match, protocol)
|
||||
|
||||
def _apply_flows(self):
|
||||
self._int_br.apply_flows()
|
||||
|
||||
def prepare_port_filter(self, port):
|
||||
LOG.debug("OFW Preparing device (%s) filter: %s", port['device'],
|
||||
port)
|
||||
vif_port = self._int_br.br.get_vif_port_by_id(port['device'])
|
||||
if not vif_port:
|
||||
LOG.warning(_LW("Port %(port_id)s not present in bridge. Skip"
|
||||
"applying rules for this port"),
|
||||
{'port_id': port})
|
||||
return
|
||||
port['vinfo'] = self._vif_port_info(vif_port.port_name)
|
||||
self._remove_flows(port)
|
||||
self._filtered_ports[port['device']] = port
|
||||
self._filtered_in_ports[port['device']] = vif_port.ofport
|
||||
self._add_base_flows(port, vif_port)
|
||||
self._add_rules_flows(port)
|
||||
|
||||
def update_port_filter(self, port):
|
||||
LOG.debug("OFW Updating device (%s) filter: %s", port['device'],
|
||||
port)
|
||||
if port['device'] not in self._filtered_ports:
|
||||
LOG.info(_('Attempted to update port filter which is not '
|
||||
'filtered %s'), port['device'])
|
||||
return
|
||||
|
||||
old_port = self._filtered_ports.get(port['device'])
|
||||
vif_port = self._int_br.br.get_vif_port_by_id(port['device'])
|
||||
if not vif_port:
|
||||
LOG.warning(_LW("Port %(port_id)s not present in bridge. Skip"
|
||||
"applying rules for this port"),
|
||||
{'port_id': port})
|
||||
return
|
||||
port['vinfo'] = self._vif_port_info(vif_port.port_name)
|
||||
self._remove_flows(old_port)
|
||||
self._filtered_ports[port['device']] = port
|
||||
self._filtered_in_ports[port['device']] = vif_port.ofport
|
||||
self._add_base_flows(port, vif_port)
|
||||
self._add_rules_flows(port)
|
||||
|
||||
def remove_port_filter(self, port):
|
||||
LOG.debug("OFW Removing device (%s) filter: %s", port['device'],
|
||||
port)
|
||||
if not self._filtered_ports.get(port['device']):
|
||||
LOG.info(_('Attempted to remove port filter which is not '
|
||||
'filtered %r'), port)
|
||||
return
|
||||
self._remove_flows(port)
|
||||
self._filtered_ports.pop(port['device'])
|
||||
self._filtered_in_ports.pop(port['device'])
|
||||
|
||||
def filter_defer_apply_on(self):
|
||||
LOG.debug("OFW defer_apply_on")
|
||||
self._deferred = True
|
||||
|
||||
def filter_defer_apply_off(self):
|
||||
LOG.debug("OFW defer_apply_off")
|
||||
if self._deferred:
|
||||
self._apply_flows()
|
||||
self._deferred = False
|
||||
|
||||
@staticmethod
|
||||
def _ip_version_from_address(ip_string):
|
||||
ipv4_pattern = \
|
||||
"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}" \
|
||||
"(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
|
||||
ipv6_pattern = "^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"
|
||||
ipv6_pattern_hexcompressed = \
|
||||
"^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]" \
|
||||
"{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$"
|
||||
ipv6_pattern_hex4dec = \
|
||||
"^((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]" \
|
||||
"\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\z"
|
||||
ipv6_pattern_hex4deccompressed = \
|
||||
"^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]" \
|
||||
"{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]" \
|
||||
"\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$"
|
||||
if re.search(ipv4_pattern, ip_string):
|
||||
return IPv4
|
||||
if re.search(ipv6_pattern, ip_string) or \
|
||||
re.search(ipv6_pattern_hexcompressed, ip_string) or \
|
||||
re.search(ipv6_pattern_hex4dec, ip_string) or \
|
||||
re.search(ipv6_pattern_hex4deccompressed, ip_string):
|
||||
return IPv6
|
||||
raise ValueError(_('Illegal IP string address'))
|
0
networking_ovs_dpdk/common/__init__.py
Normal file
0
networking_ovs_dpdk/common/__init__.py
Normal file
47
networking_ovs_dpdk/common/_i18n.py
Normal file
47
networking_ovs_dpdk/common/_i18n.py
Normal file
@ -0,0 +1,47 @@
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
# derived from:
|
||||
# http://docs.openstack.org/developer/oslo.i18n/usage.html
|
||||
|
||||
import oslo_i18n
|
||||
|
||||
DOMAIN = "networking-ovs-dpdk"
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# The contextual translation function using the name "_C"
|
||||
# requires oslo.i18n >=2.1.0
|
||||
_C = _translators.contextual_form
|
||||
|
||||
# The plural translation function using the name "_P"
|
||||
# requires oslo.i18n >=2.1.0
|
||||
_P = _translators.plural_form
|
||||
|
||||
# Translators for log levels.
|
||||
#
|
||||
# The abbreviated names are meant to reflect the usual use of a short
|
||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||
# the level.
|
||||
_LI = _translators.log_info
|
||||
_LW = _translators.log_warning
|
||||
_LE = _translators.log_error
|
||||
_LC = _translators.log_critical
|
||||
|
||||
|
||||
def get_available_languages():
|
||||
return oslo_i18n.get_available_languages(DOMAIN)
|
105
networking_ovs_dpdk/common/config.py
Normal file
105
networking_ovs_dpdk/common/config.py
Normal file
@ -0,0 +1,105 @@
|
||||
# Copyright 2012 Red Hat, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from networking_ovs_dpdk.common import constants
|
||||
from neutron.agent.common import config
|
||||
from neutron.plugins.common import constants as p_const
|
||||
|
||||
|
||||
DEFAULT_BRIDGE_MAPPINGS = []
|
||||
DEFAULT_VLAN_RANGES = []
|
||||
DEFAULT_TUNNEL_RANGES = []
|
||||
DEFAULT_TUNNEL_TYPES = []
|
||||
|
||||
ovs_opts = [
|
||||
cfg.StrOpt('integration_bridge', default='br-int',
|
||||
help=_("Integration bridge to use.")),
|
||||
cfg.StrOpt('tunnel_bridge', default='br-tun',
|
||||
help=_("Tunnel bridge to use.")),
|
||||
cfg.StrOpt('int_peer_patch_port', default='patch-tun',
|
||||
help=_("Peer patch port in integration bridge for tunnel "
|
||||
"bridge.")),
|
||||
cfg.StrOpt('tun_peer_patch_port', default='patch-int',
|
||||
help=_("Peer patch port in tunnel bridge for integration "
|
||||
"bridge.")),
|
||||
cfg.IPOpt('local_ip', version=4,
|
||||
help=_("Local IP address of tunnel endpoint.")),
|
||||
cfg.ListOpt('bridge_mappings',
|
||||
default=DEFAULT_BRIDGE_MAPPINGS,
|
||||
help=_("List of <physical_network>:<bridge>. "
|
||||
"Deprecated for ofagent.")),
|
||||
cfg.BoolOpt('use_veth_interconnection', default=False,
|
||||
help=_("Use veths instead of patch ports to interconnect the "
|
||||
"integration bridge to physical bridges.")),
|
||||
cfg.StrOpt('of_interface', default='ovsdpdk-ofctl',
|
||||
choices=['ovs-ofctl', 'ovsdpdk-ofctl'],
|
||||
help=_("OpenFlow interface to use.")),
|
||||
]
|
||||
|
||||
agent_opts = [
|
||||
cfg.IntOpt('polling_interval', default=2,
|
||||
help=_("The number of seconds the agent will wait between "
|
||||
"polling for local device changes.")),
|
||||
cfg.BoolOpt('minimize_polling',
|
||||
default=True,
|
||||
help=_("Minimize polling by monitoring ovsdb for interface "
|
||||
"changes.")),
|
||||
cfg.IntOpt('ovsdb_monitor_respawn_interval',
|
||||
default=constants.DEFAULT_OVSDBMON_RESPAWN,
|
||||
help=_("The number of seconds to wait before respawning the "
|
||||
"ovsdb monitor after losing communication with it.")),
|
||||
cfg.ListOpt('tunnel_types', default=DEFAULT_TUNNEL_TYPES,
|
||||
help=_("Network types supported by the agent "
|
||||
"(gre and/or vxlan).")),
|
||||
cfg.IntOpt('vxlan_udp_port', default=p_const.VXLAN_UDP_PORT,
|
||||
help=_("The UDP port to use for VXLAN tunnels.")),
|
||||
cfg.IntOpt('veth_mtu',
|
||||
help=_("MTU size of veth interfaces")),
|
||||
cfg.BoolOpt('l2_population', default=False,
|
||||
help=_("Use ML2 l2population mechanism driver to learn "
|
||||
"remote MAC and IPs and improve tunnel scalability.")),
|
||||
cfg.BoolOpt('arp_responder', default=False,
|
||||
help=_("Enable local ARP responder if it is supported. "
|
||||
"Requires OVS 2.1 and ML2 l2population driver. "
|
||||
"Allows the switch (when supporting an overlay) "
|
||||
"to respond to an ARP request locally without "
|
||||
"performing a costly ARP broadcast into the overlay.")),
|
||||
cfg.BoolOpt('prevent_arp_spoofing', default=True,
|
||||
help=_("Enable suppression of ARP responses that don't match "
|
||||
"an IP address that belongs to the port from which "
|
||||
"they originate. Note: This prevents the VMs attached "
|
||||
"to this agent from spoofing, it doesn't protect them "
|
||||
"from other devices which have the capability to spoof "
|
||||
"(e.g. bare metal or VMs attached to agents without "
|
||||
"this flag set to True). Spoofing rules will not be "
|
||||
"added to any ports that have port security disabled. "
|
||||
"This requires a version of OVS that supports matching "
|
||||
"ARP headers.")),
|
||||
cfg.BoolOpt('dont_fragment', default=True,
|
||||
help=_("Set or un-set the don't fragment (DF) bit on "
|
||||
"outgoing IP packet carrying GRE/VXLAN tunnel.")),
|
||||
cfg.BoolOpt('enable_distributed_routing', default=False,
|
||||
help=_("Make the l2 agent run in DVR mode.")),
|
||||
cfg.IntOpt('quitting_rpc_timeout', default=10,
|
||||
help=_("Set new timeout in seconds for new rpc calls after "
|
||||
"agent receives SIGTERM. If value is set to 0, rpc "
|
||||
"timeout won't be changed"))
|
||||
]
|
||||
|
||||
|
||||
cfg.CONF.register_opts(ovs_opts, "OVS")
|
||||
cfg.CONF.register_opts(agent_opts, "AGENT")
|
||||
config.register_agent_state_opts_helper(cfg.CONF)
|
90
networking_ovs_dpdk/common/constants.py
Normal file
90
networking_ovs_dpdk/common/constants.py
Normal file
@ -0,0 +1,90 @@
|
||||
# Copyright (c) 2012 OpenStack Foundation.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from neutron.plugins.common import constants as p_const
|
||||
|
||||
|
||||
# Special vlan_id value in ovs_vlan_allocations table indicating flat network
|
||||
FLAT_VLAN_ID = -1
|
||||
|
||||
# Topic for tunnel notifications between the plugin and agent
|
||||
TUNNEL = 'tunnel'
|
||||
|
||||
# Name prefixes for veth device or patch port pair linking the integration
|
||||
# bridge with the physical bridge for a physical network
|
||||
PEER_INTEGRATION_PREFIX = 'int-'
|
||||
PEER_PHYSICAL_PREFIX = 'phy-'
|
||||
|
||||
# Nonexistent peer used to create patch ports without associating them, it
|
||||
# allows to define flows before association
|
||||
NONEXISTENT_PEER = 'nonexistent-peer'
|
||||
|
||||
# The different types of tunnels
|
||||
TUNNEL_NETWORK_TYPES = [p_const.TYPE_GRE, p_const.TYPE_VXLAN]
|
||||
|
||||
# Various tables for DVR use of integration bridge flows
|
||||
LOCAL_SWITCHING = 0
|
||||
DVR_TO_SRC_MAC = 1
|
||||
DVR_TO_SRC_MAC_VLAN = 2
|
||||
|
||||
# Various tables for tunneling flows
|
||||
DVR_PROCESS = 1
|
||||
PATCH_LV_TO_TUN = 2
|
||||
GRE_TUN_TO_LV = 3
|
||||
VXLAN_TUN_TO_LV = 4
|
||||
DVR_NOT_LEARN = 9
|
||||
LEARN_FROM_TUN = 10
|
||||
UCAST_TO_TUN = 20
|
||||
ARP_RESPONDER = 21
|
||||
FLOOD_TO_TUN = 22
|
||||
|
||||
# Various tables for DVR use of physical bridge flows
|
||||
DVR_PROCESS_VLAN = 1
|
||||
LOCAL_VLAN_TRANSLATION = 2
|
||||
DVR_NOT_LEARN_VLAN = 3
|
||||
|
||||
# Tables for integration bridge
|
||||
# Table 0 is used for forwarding.
|
||||
CANARY_TABLE = 23
|
||||
|
||||
# Table for ARP poison/spoofing prevention rules
|
||||
ARP_SPOOF_TABLE = 24
|
||||
|
||||
# type for ARP reply in ARP header
|
||||
ARP_REPLY = '0x2'
|
||||
|
||||
# Map tunnel types to tables number
|
||||
TUN_TABLE = {p_const.TYPE_GRE: GRE_TUN_TO_LV,
|
||||
p_const.TYPE_VXLAN: VXLAN_TUN_TO_LV}
|
||||
|
||||
# The default respawn interval for the ovsdb monitor
|
||||
DEFAULT_OVSDBMON_RESPAWN = 30
|
||||
|
||||
# Represent invalid OF Port
|
||||
OFPORT_INVALID = -1
|
||||
|
||||
ARP_RESPONDER_ACTIONS = ('move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],'
|
||||
'mod_dl_src:%(mac)s,'
|
||||
'load:0x2->NXM_OF_ARP_OP[],'
|
||||
'move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],'
|
||||
'move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],'
|
||||
'load:%(mac)#x->NXM_NX_ARP_SHA[],'
|
||||
'load:%(ip)#x->NXM_OF_ARP_SPA[],'
|
||||
'in_port')
|
||||
|
||||
# Represent ovs status
|
||||
OVS_RESTARTED = 0
|
||||
OVS_NORMAL = 1
|
||||
OVS_DEAD = 2
|
0
networking_ovs_dpdk/tests/__init__.py
Normal file
0
networking_ovs_dpdk/tests/__init__.py
Normal file
0
networking_ovs_dpdk/tests/unit/__init__.py
Normal file
0
networking_ovs_dpdk/tests/unit/__init__.py
Normal file
0
networking_ovs_dpdk/tests/unit/agent/__init__.py
Normal file
0
networking_ovs_dpdk/tests/unit/agent/__init__.py
Normal file
1703
networking_ovs_dpdk/tests/unit/agent/test_ovs_dpdk_firewall.py
Normal file
1703
networking_ovs_dpdk/tests/unit/agent/test_ovs_dpdk_firewall.py
Normal file
File diff suppressed because it is too large
Load Diff
6
openstack-common.conf
Normal file
6
openstack-common.conf
Normal file
@ -0,0 +1,6 @@
|
||||
[DEFAULT]
|
||||
|
||||
# The list of modules to copy from oslo-incubator.git
|
||||
|
||||
# The base module to hold the copy of openstack.common
|
||||
base=networking_ovs_dpdk
|
@ -124,6 +124,12 @@
|
||||
# Patches are downloaded and applied in the same order they are listed here.
|
||||
# Example: ovs_dpdk_patches='file:///root/dpdk1.patch file:///root/dpdk2.patch'
|
||||
#
|
||||
# [*ovs_enable_sg_firewall_multicast*]
|
||||
# *todo
|
||||
#
|
||||
# [*ovs_multicast_snooping_aging_time*]
|
||||
# *todo
|
||||
#
|
||||
# [*ovs_emc_size*]
|
||||
# (number) Defines the value which will be replaced in constant EM_FLOW_HASH_SHIFT in ovs lib/dpif-netdev.c.
|
||||
# The constant represents count of bits for hash.
|
||||
|
@ -5,3 +5,7 @@
|
||||
|
||||
|
||||
pbr>=0.11,<2.0
|
||||
Babel>=1.3
|
||||
six>=1.9.0
|
||||
netaddr>=0.7.12
|
||||
oslo.service>=0.1.0 # Apache-2.0
|
||||
|
29
setup.cfg
29
setup.cfg
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = networking-ovs-dpdk
|
||||
summary = A collection of installation tools to deploy DPDK accelerated Open vSwitch with OpenStack
|
||||
summary = A collection of drivers and installation tools to deploy and manage DPDK accelerated Open vSwitch with Openstack.
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
@ -12,6 +12,15 @@ classifier =
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.4
|
||||
|
||||
[files]
|
||||
packages =
|
||||
networking_ovs_dpdk
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
@ -25,3 +34,21 @@ upload-dir = doc/build/html
|
||||
all_files = 1
|
||||
build-dir = releasenotes/build
|
||||
source-dir = releasenotes/source
|
||||
|
||||
[compile_catalog]
|
||||
directory = networking_ovs_dpdk/locale
|
||||
domain = networking-ovs-dpdk
|
||||
|
||||
[update_catalog]
|
||||
domain = networking-ovs-dpdk
|
||||
output_dir = networking_ovs_dpdk/locale
|
||||
input_file = networking_ovs_dpdk/locale/networking-ovs-dpdk.pot
|
||||
|
||||
[extract_messages]
|
||||
keywords = _ gettext ngettext l_ lazy_gettext
|
||||
mapping_file = babel.cfg
|
||||
output_file = networking_ovs_dpdk/locale/networking-ovs-dpdk.pot
|
||||
|
||||
[entry_points]
|
||||
neutron.agent.firewall_drivers =
|
||||
ovs_learn_action = networking_ovs_dpdk.agent.ovs_dpdk_firewall:OVSFirewallDriver
|
||||
|
@ -2,9 +2,23 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
coverage>=3.6
|
||||
hacking>=0.9.2,<0.10
|
||||
|
||||
coverage>=4.0
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
discover
|
||||
python-subunit
|
||||
sphinx>=1.1.2
|
||||
oslosphinx
|
||||
oslotest>=1.1.0.0a1
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=0.9.34
|
||||
flake8==2.1.0
|
||||
tempest-lib>=0.2.0
|
||||
vine>=1.1.3
|
||||
|
||||
# releasenotes
|
||||
reno>=1.6.2 # Apache2
|
||||
|
||||
-e git+https://github.com/openstack/neutron.git@master#egg=neutron
|
||||
|
29
tox.ini
29
tox.ini
@ -1,6 +1,6 @@
|
||||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = docs
|
||||
envlist = py27,pep8,docs
|
||||
|
||||
skipsdist = True
|
||||
sitepackages=False
|
||||
@ -18,12 +18,7 @@ commands =
|
||||
# mode. To do this define the TRACE_FAILONLY environmental variable.
|
||||
|
||||
[testenv:pep8]
|
||||
commands = /bin/true
|
||||
ignore_outcome = True
|
||||
|
||||
[testenv:py27]
|
||||
commands = /bin/true
|
||||
ignore_outcome = True
|
||||
commands = flake8
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
@ -37,3 +32,23 @@ commands = python setup.py build_sphinx
|
||||
[testenv:releasenotes]
|
||||
commands =
|
||||
sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
|
||||
|
||||
[flake8]
|
||||
# H803 skipped on purpose per list discussion.
|
||||
# E123, E125 skipped as they are invalid PEP-8.
|
||||
# E125 continuation line does not distinguish itself from next logical line
|
||||
# E126 continuation line over-indented for hanging indent
|
||||
# E128 continuation line under-indented for visual indent
|
||||
# E129 visually indented line with same indent as next logical line
|
||||
# E265 block comment should start with ‘# ‘
|
||||
# H301 one import per line
|
||||
# H305 imports not grouped correctly
|
||||
# H307 like imports should be grouped together
|
||||
# H402 one line docstring needs punctuation
|
||||
# H404 multi line docstring should start with a summary
|
||||
# H405 multi line docstring summary not separated with an empty line
|
||||
# H904 Wrap long lines in parentheses instead of a backslash
|
||||
ignore = E123,E125,E126,E128,E129,E265,H301,H305,H307,H402,H404,H405,H904,H803
|
||||
show-source = True
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
|
||||
|
Loading…
x
Reference in New Issue
Block a user