Enable GRE and VXLAN with the same ID

Current packet processing in br-tun is based on tun-id,
as a consequence, two networks using different tunnel
types but sharing the same tun-id would not be properly isolated.

To ensure proper isolation within a single bridge, NORMAL action
can't be used any more as it floods unknown unicasts on all
bridges ports. It is replaced by a learn action that dynamically
sets-up flows when packets are recieved from tunnel ports. As mac
address are learnt in explicit flows (in table 20), we can use a
default action in that table to flood unknown unicasts to the
right set of ports, like broadcasts and multicasts packets.

See https://wiki.openstack.org/wiki/Ovs-flow-logic for a more
detailled explanation of the flow logic

Another alternative could have been to use distinct bridges for
each tunnel type (whithout modifying the current flow logic),
but previous alternative may be preferable as it paves the way
for new tunneling optimisations (like RPC based mac learning and
partial-mesh flooding proposed in bp/l2-population)

Change-Id: I1dfe74f96680c2c6fe4d8d4aac4821c6b020c005
Closes-Bug: #1196963
This commit is contained in:
Francois Eleouet 2013-08-07 11:19:46 +02:00
parent 1c2e111a0b
commit a369f9e396
5 changed files with 191 additions and 73 deletions

View File

@ -126,6 +126,8 @@ class OVSBridge:
elif 'priority' in kwargs:
raise Exception(_("Cannot match priority on flow deletion"))
table = ('table' in kwargs and ",table=%s" %
kwargs['table'] or '')
in_port = ('in_port' in kwargs and ",in_port=%s" %
kwargs['in_port'] or '')
dl_type = ('dl_type' in kwargs and ",dl_type=%s" %
@ -139,14 +141,14 @@ class OVSBridge:
tun_id = 'tun_id' in kwargs and ",tun_id=%s" % kwargs['tun_id'] or ''
proto = 'proto' in kwargs and ",%s" % kwargs['proto'] or ''
ip = ('nw_src' in kwargs or 'nw_dst' in kwargs) and ',ip' or ''
match = (in_port + dl_type + dl_vlan + dl_src + dl_dst +
match = (table + in_port + dl_type + dl_vlan + dl_src + dl_dst +
(ip or proto) + nw_src + nw_dst + tun_id)
if match:
match = match[1:] # strip leading comma
flow_expr_arr.append(match)
return flow_expr_arr
def add_flow(self, **kwargs):
def add_or_mod_flow_str(self, **kwargs):
if "actions" not in kwargs:
raise Exception(_("Must specify one or more actions"))
if "priority" not in kwargs:
@ -155,8 +157,16 @@ class OVSBridge:
flow_expr_arr = self._build_flow_expr_arr(**kwargs)
flow_expr_arr.append("actions=%s" % (kwargs["actions"]))
flow_str = ",".join(flow_expr_arr)
return flow_str
def add_flow(self, **kwargs):
flow_str = self.add_or_mod_flow_str(**kwargs)
self.run_ofctl("add-flow", [flow_str])
def mod_flow(self, **kwargs):
flow_str = self.add_or_mod_flow_str(**kwargs)
self.run_ofctl("mod-flows", [flow_str])
def delete_flows(self, **kwargs):
kwargs['delete'] = True
flow_expr_arr = self._build_flow_expr_arr(**kwargs)

View File

@ -172,6 +172,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
self.int_br = self.setup_integration_br(integ_br)
self.setup_physical_bridges(bridge_mappings)
self.local_vlan_map = {}
self.tun_br_ofports = {constants.TYPE_GRE: set(),
constants.TYPE_VXLAN: set()}
self.polling_interval = polling_interval
@ -307,8 +309,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
if tunnel_ip == self.local_ip:
return
tun_name = '%s-%s' % (tunnel_type, tunnel_id)
self.tun_br.add_tunnel_port(tun_name, tunnel_ip, self.local_ip,
tunnel_type, self.vxlan_udp_port)
self.setup_tunnel_port(tun_name, tunnel_ip, tunnel_type)
def create_rpc_dispatcher(self):
'''Get the rpc dispatcher for this manager.
@ -342,18 +343,20 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
if network_type in constants.TUNNEL_NETWORK_TYPES:
if self.enable_tunneling:
# outbound
self.tun_br.add_flow(priority=4, in_port=self.patch_int_ofport,
# outbound broadcast/multicast
ofports = ','.join(self.tun_br_ofports[network_type])
self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
priority=1,
dl_vlan=lvid,
actions="set_tunnel:%s,normal" %
segmentation_id)
# inbound bcast/mcast
self.tun_br.add_flow(
priority=3,
tun_id=segmentation_id,
dl_dst="01:00:00:00:00:00/01:00:00:00:00:00",
actions="mod_vlan_vid:%s,output:%s" %
(lvid, self.patch_int_ofport))
actions="strip_vlan,set_tunnel:%s,"
"output:%s" % (segmentation_id, ofports))
# inbound from tunnels: set lvid in the right table
# and resubmit to Table LEARN_FROM_TUN for mac learning
self.tun_br.add_flow(table=constants.TUN_TABLE[network_type],
priority=1,
tun_id=segmentation_id,
actions="mod_vlan_vid:%s,resubmit(,%s)" %
(lvid, constants.LEARN_FROM_TUN))
else:
LOG.error(_("Cannot provision %(network_type)s network for "
"net-id=%(net_uuid)s - tunneling disabled"),
@ -421,7 +424,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
if lvm.network_type in constants.TUNNEL_NETWORK_TYPES:
if self.enable_tunneling:
self.tun_br.delete_flows(tun_id=lvm.segmentation_id)
self.tun_br.delete_flows(
table=constants.TUN_TABLE[lvm.network_type],
tun_id=lvm.segmentation_id)
self.tun_br.delete_flows(dl_vlan=lvm.vlan)
elif lvm.network_type == constants.TYPE_FLAT:
if lvm.physical_network in self.phys_brs:
@ -474,14 +479,6 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
lvm = self.local_vlan_map[net_uuid]
lvm.vif_ports[port.vif_id] = port
if network_type in constants.TUNNEL_NETWORK_TYPES:
if self.enable_tunneling:
# inbound unicast
self.tun_br.add_flow(priority=3, tun_id=segmentation_id,
dl_dst=port.vif_mac,
actions="mod_vlan_vid:%s,normal" %
lvm.vlan)
self.int_br.set_db_attribute("Port", port.port_name, "tag",
str(lvm.vlan))
if int(port.ofport) != -1:
@ -503,18 +500,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
LOG.info(_('port_unbound() net_uuid %s not in local_vlan_map'),
net_uuid)
return
lvm = self.local_vlan_map[net_uuid]
vif_port = lvm.vif_ports.pop(vif_id, None)
if vif_port:
if self.enable_tunneling and lvm.network_type in (
constants.TUNNEL_NETWORK_TYPES):
# remove inbound unicast flow
self.tun_br.delete_flows(tun_id=lvm.segmentation_id,
dl_dst=vif_port.vif_mac)
else:
LOG.info(_('port_unbound: vif_id %s not in local_vlan_map'),
vif_id)
lvm = self.local_vlan_map[net_uuid]
lvm.vif_ports.pop(vif_id, None)
if not lvm.vif_ports:
self.reclaim_local_vlan(net_uuid, lvm)
@ -590,7 +578,58 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
"Agent terminated!"))
exit(1)
self.tun_br.remove_all_flows()
self.tun_br.add_flow(priority=1, actions="drop")
# Table 0 (default) will sort incoming traffic depending on in_port
self.tun_br.add_flow(priority=1,
in_port=self.patch_int_ofport,
actions="resubmit(,%s)" %
constants.PATCH_LV_TO_TUN)
self.tun_br.add_flow(priority=0, actions="drop")
# PATCH_LV_TO_TUN table will handle packets coming from patch_int
# unicasts go to table UCAST_TO_TUN where remote adresses are learnt
self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN,
dl_dst="00:00:00:00:00:00/01:00:00:00:00:00",
actions="resubmit(,%s)" % constants.UCAST_TO_TUN)
# Broadcasts/multicasts go to table FLOOD_TO_TUN that handles flooding
self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN,
dl_dst="01:00:00:00:00:00/01:00:00:00:00:00",
actions="resubmit(,%s)" % constants.FLOOD_TO_TUN)
# Tables [tunnel_type]_TUN_TO_LV will set lvid depending on tun_id
# for each tunnel type, and resubmit to table LEARN_FROM_TUN where
# remote mac adresses will be learnt
for tunnel_type in constants.TUNNEL_NETWORK_TYPES:
self.tun_br.add_flow(table=constants.TUN_TABLE[tunnel_type],
priority=0,
actions="drop")
# LEARN_FROM_TUN table will have a single flow using a learn action to
# dynamically set-up flows in UCAST_TO_TUN corresponding to remote mac
# adresses (assumes that lvid has already been set by a previous flow)
learned_flow = ("table=%s,"
"priority=1,"
"hard_timeout=300,"
"NXM_OF_VLAN_TCI[0..11],"
"load:0->NXM_OF_VLAN_TCI[],"
"load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],"
"output:NXM_OF_IN_PORT[]" %
constants.UCAST_TO_TUN)
# Once remote mac adresses are learnt, packet is outputed to patch_int
self.tun_br.add_flow(table=constants.LEARN_FROM_TUN,
priority=1,
actions="learn(%s),output:%s" %
(learned_flow, self.patch_int_ofport))
# Egress unicast will be handled in table UCAST_TO_TUN, where remote
# mac adresses will be learned. For now, just add a default flow that
# will resubmit unknown unicasts to table FLOOD_TO_TUN to treat them
# as broadcasts/multicasts
self.tun_br.add_flow(table=constants.UCAST_TO_TUN,
priority=0,
actions="resubmit(,%s)" %
constants.FLOOD_TO_TUN)
# FLOOD_TO_TUN will handle flooding in tunnels based on lvid,
# for now, add a default drop action
self.tun_br.add_flow(table=constants.FLOOD_TO_TUN,
priority=0,
actions="drop")
def setup_physical_bridges(self, bridge_mappings):
'''Setup the physical network bridges.
@ -685,6 +724,35 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
else:
LOG.debug(_("No VIF port for port %s defined on agent."), port_id)
def setup_tunnel_port(self, port_name, remote_ip, tunnel_type):
ofport = self.tun_br.add_tunnel_port(port_name,
remote_ip,
self.local_ip,
tunnel_type,
self.vxlan_udp_port)
if ofport < 0:
LOG.error(_("Failed to set-up %(type)s tunnel port to %(ip)s"),
{'type': tunnel_type, 'ip': remote_ip})
else:
self.tun_br_ofports[tunnel_type].add(ofport)
# Add flow in default table to resubmit to the right
# tunelling table (lvid will be set in the latter)
self.tun_br.add_flow(priority=1,
in_port=ofport,
actions="resubmit(,%s)" %
constants.TUN_TABLE[tunnel_type])
# Update flooding flows to include the new tunnel
ofports = ','.join(self.tun_br_ofports[tunnel_type])
for network_id, vlan_mapping in self.local_vlan_map.iteritems():
if vlan_mapping.network_type == tunnel_type:
self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
priority=1,
dl_vlan=vlan_mapping.vlan,
actions="strip_vlan,"
"set_tunnel:%s,output:%s" %
(vlan_mapping.segmentation_id,
ofports))
def treat_devices_added(self, devices):
resync = False
self.sg_agent.prepare_devices_filter(devices)
@ -806,11 +874,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
if self.local_ip != tunnel['ip_address']:
tunnel_id = tunnel.get('id', tunnel['ip_address'])
tun_name = '%s-%s' % (tunnel_type, tunnel_id)
self.tun_br.add_tunnel_port(tun_name,
tunnel['ip_address'],
self.local_ip,
tunnel_type,
self.vxlan_udp_port)
self.setup_tunnel_port(tun_name,
tunnel['ip_address'],
tunnel_type)
except Exception as e:
LOG.debug(_("Unable to sync tunnel IP %(local_ip)s: %(e)s"),
{'local_ip': self.local_ip, 'e': e})

View File

@ -38,3 +38,13 @@ MINIMUM_OVS_VXLAN_VERSION = "1.10"
# The different types of tunnels
TUNNEL_NETWORK_TYPES = [TYPE_GRE, TYPE_VXLAN]
# Various tables for tunneling flows
PATCH_LV_TO_TUN = 1
GRE_TUN_TO_LV = 2
VXLAN_TUN_TO_LV = 3
LEARN_FROM_TUN = 10
UCAST_TO_TUN = 20
FLOOD_TO_TUN = 21
# Map tunnel types to tables number
TUN_TABLE = {TYPE_GRE: GRE_TUN_TO_LV, TYPE_VXLAN: VXLAN_TUN_TO_LV}

View File

@ -331,17 +331,13 @@ class TestOvsNeutronAgent(base.BaseTestCase):
"int_ofport")
def test_port_unbound(self):
with contextlib.nested(
mock.patch.object(self.agent.tun_br, "delete_flows"),
mock.patch.object(self.agent, "reclaim_local_vlan")
) as (delfl_fn, reclvl_fn):
with mock.patch.object(self.agent, "reclaim_local_vlan") as reclvl_fn:
self.agent.enable_tunneling = True
lvm = mock.Mock()
lvm.network_type = "gre"
lvm.vif_ports = {"vif1": mock.Mock()}
self.agent.local_vlan_map["netuid12345"] = lvm
self.agent.port_unbound("vif1", "netuid12345")
self.assertTrue(delfl_fn.called)
self.assertTrue(reclvl_fn.called)
reclvl_fn.called = False

View File

@ -37,13 +37,20 @@ VIF_MAC = '3c:09:24:1e:78:23'
OFPORT_NUM = 1
VIF_PORT = ovs_lib.VifPort('port', OFPORT_NUM,
VIF_ID, VIF_MAC, 'switch')
VIF_PORTS = {LV_ID: VIF_PORT}
VIF_PORTS = {VIF_ID: VIF_PORT}
LVM = ovs_neutron_agent.LocalVLANMapping(LV_ID, 'gre', None, LS_ID, VIF_PORTS)
LVM_FLAT = ovs_neutron_agent.LocalVLANMapping(
LV_ID, 'flat', 'net1', LS_ID, VIF_PORTS)
LVM_VLAN = ovs_neutron_agent.LocalVLANMapping(
LV_ID, 'vlan', 'net1', LS_ID, VIF_PORTS)
GRE_OFPORTS = set(['11', '12'])
VXLAN_OFPORTS = set(['13', '14'])
TUN_OFPORTS = {constants.TYPE_GRE: GRE_OFPORTS,
constants.TYPE_VXLAN: VXLAN_OFPORTS}
BCAST_MAC = "01:00:00:00:00:00/01:00:00:00:00:00"
UCAST_MAC = "00:00:00:00:00:00/01:00:00:00:00:00"
class DummyPort:
@ -108,8 +115,45 @@ class TunnelTest(base.BaseTestCase):
'patch-tun', 'patch-int').AndReturn(self.TUN_OFPORT)
self.mock_tun_bridge.add_patch_port(
'patch-int', 'patch-tun').AndReturn(self.INT_OFPORT)
self.mock_tun_bridge.remove_all_flows()
self.mock_tun_bridge.add_flow(priority=1, actions='drop')
self.mock_tun_bridge.add_flow(priority=1,
in_port=self.INT_OFPORT,
actions="resubmit(,%s)" %
constants.PATCH_LV_TO_TUN)
self.mock_tun_bridge.add_flow(priority=0, actions='drop')
self.mock_tun_bridge.add_flow(table=constants.PATCH_LV_TO_TUN,
dl_dst=UCAST_MAC,
actions="resubmit(,%s)" %
constants.UCAST_TO_TUN)
self.mock_tun_bridge.add_flow(table=constants.PATCH_LV_TO_TUN,
dl_dst=BCAST_MAC,
actions="resubmit(,%s)" %
constants.FLOOD_TO_TUN)
for tunnel_type in constants.TUNNEL_NETWORK_TYPES:
self.mock_tun_bridge.add_flow(
table=constants.TUN_TABLE[tunnel_type],
priority=0,
actions="drop")
learned_flow = ("table=%s,"
"priority=1,"
"hard_timeout=300,"
"NXM_OF_VLAN_TCI[0..11],"
"load:0->NXM_OF_VLAN_TCI[],"
"load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],"
"output:NXM_OF_IN_PORT[]" %
constants.UCAST_TO_TUN)
self.mock_tun_bridge.add_flow(table=constants.LEARN_FROM_TUN,
priority=1,
actions="learn(%s),output:%s" %
(learned_flow, self.INT_OFPORT))
self.mock_tun_bridge.add_flow(table=constants.UCAST_TO_TUN,
priority=0,
actions="resubmit(,%s)" %
constants.FLOOD_TO_TUN)
self.mock_tun_bridge.add_flow(table=constants.FLOOD_TO_TUN,
priority=0,
actions="drop")
self.mox.StubOutWithMock(ip_lib, 'device_exists')
ip_lib.device_exists('tunnel_bridge_mapping', 'sudo').AndReturn(True)
@ -153,14 +197,18 @@ class TunnelTest(base.BaseTestCase):
self.mox.VerifyAll()
def testProvisionLocalVlan(self):
action_string = 'set_tunnel:%s,normal' % LS_ID
self.mock_tun_bridge.add_flow(priority=4, in_port=self.INT_OFPORT,
dl_vlan=LV_ID, actions=action_string)
action_string = 'mod_vlan_vid:%s,output:%s' % (LV_ID, self.INT_OFPORT)
self.mock_tun_bridge.add_flow(priority=3, tun_id=LS_ID,
dl_dst=BCAST_MAC, actions=action_string)
self.mock_tun_bridge.mod_flow(table=constants.FLOOD_TO_TUN,
priority=1,
dl_vlan=LV_ID,
actions="strip_vlan,"
"set_tunnel:%s,output:%s" %
(LS_ID, ','.join(GRE_OFPORTS)))
self.mock_tun_bridge.add_flow(table=constants.TUN_TABLE['gre'],
priority=1,
tun_id=LS_ID,
actions="mod_vlan_vid:%s,resubmit(,%s)" %
(LV_ID, constants.LEARN_FROM_TUN))
self.mox.ReplayAll()
a = ovs_neutron_agent.OVSNeutronAgent(self.INT_BRIDGE,
@ -169,6 +217,7 @@ class TunnelTest(base.BaseTestCase):
'sudo', 2, ['gre'],
self.VETH_MTU)
a.available_local_vlans = set([LV_ID])
a.tun_br_ofports = TUN_OFPORTS
a.provision_local_vlan(NET_UUID, constants.TYPE_GRE, None, LS_ID)
self.mox.VerifyAll()
@ -240,8 +289,8 @@ class TunnelTest(base.BaseTestCase):
self.mox.VerifyAll()
def testReclaimLocalVlan(self):
self.mock_tun_bridge.delete_flows(tun_id=LVM.segmentation_id)
self.mock_tun_bridge.delete_flows(
table=constants.TUN_TABLE['gre'], tun_id=LS_ID)
self.mock_tun_bridge.delete_flows(dl_vlan=LVM.vlan)
self.mox.ReplayAll()
@ -307,11 +356,6 @@ class TunnelTest(base.BaseTestCase):
'tag', str(LVM.vlan))
self.mock_int_bridge.delete_flows(in_port=VIF_PORT.ofport)
action_string = 'mod_vlan_vid:%s,normal' % LV_ID
self.mock_tun_bridge.add_flow(priority=3, tun_id=LS_ID,
dl_dst=VIF_PORT.vif_mac,
actions=action_string)
self.mox.ReplayAll()
a = ovs_neutron_agent.OVSNeutronAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
@ -323,15 +367,10 @@ class TunnelTest(base.BaseTestCase):
self.mox.VerifyAll()
def testPortUnbound(self):
self.mock_int_bridge.set_db_attribute('Port', VIF_PORT.port_name,
'tag', str(LVM.vlan))
self.mock_int_bridge.delete_flows(in_port=VIF_PORT.ofport)
self.mox.StubOutWithMock(
ovs_neutron_agent.OVSNeutronAgent, 'reclaim_local_vlan')
ovs_neutron_agent.OVSNeutronAgent.reclaim_local_vlan(NET_UUID, LVM)
action_string = 'mod_vlan_vid:%s,normal' % LV_ID
self.mock_tun_bridge.add_flow(priority=3, tun_id=LS_ID,
dl_dst=VIF_PORT.vif_mac,
actions=action_string)
self.mock_tun_bridge.delete_flows(dl_dst=VIF_MAC, tun_id=LS_ID)
self.mox.ReplayAll()
a = ovs_neutron_agent.OVSNeutronAgent(self.INT_BRIDGE,
@ -340,9 +379,6 @@ class TunnelTest(base.BaseTestCase):
'sudo', 2, ['gre'],
self.VETH_MTU)
a.local_vlan_map[NET_UUID] = LVM
a.port_bound(VIF_PORT, NET_UUID, 'gre', None, LS_ID)
a.available_local_vlans = set([LV_ID])
a.local_vlan_map[NET_UUID] = LVM
a.port_unbound(VIF_ID, NET_UUID)
self.mox.VerifyAll()