Skip to content

Commit 3e934f4

Browse files
authored
Merge pull request #3371 from anarkiwi/reflectlearn
Fix missing VLAN learn rule after warm start, don't learn host on external port if another stack DP has learned it locally.
2 parents 84f88c8 + 5ccb94e commit 3e934f4

File tree

4 files changed

+93
-45
lines changed

4 files changed

+93
-45
lines changed

faucet/valve.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -345,31 +345,30 @@ def _add_default_flows(self):
345345
ofmsgs.extend(self._add_default_drop_flows())
346346
return ofmsgs
347347

348-
def _add_vlan(self, vlan):
348+
def add_vlan(self, vlan):
349349
"""Configure a VLAN."""
350350
self.logger.info('Configuring %s' % vlan)
351351
ofmsgs = []
352352
for manager in self._get_managers():
353353
ofmsgs.extend(manager.add_vlan(vlan))
354-
vlan.reset_caches()
355354
return ofmsgs
356355

357-
def _add_vlans(self, vlans):
356+
def add_vlans(self, vlans):
358357
ofmsgs = []
359358
for vlan in vlans:
360-
ofmsgs.extend(self._add_vlan(vlan))
359+
ofmsgs.extend(self.add_vlan(vlan))
361360
return ofmsgs
362361

363-
def _del_vlan(self, vlan):
362+
def del_vlan(self, vlan):
364363
"""Delete a configured VLAN."""
365364
self.logger.info('Delete VLAN %s' % vlan)
366365
table = valve_table.wildcard_table
367366
return [table.flowdel(match=table.match(vlan=vlan))]
368367

369-
def _del_vlans(self, vlans):
368+
def del_vlans(self, vlans):
370369
ofmsgs = []
371370
for vlan in vlans:
372-
ofmsgs.extend(self._del_vlan(vlan))
371+
ofmsgs.extend(self.del_vlan(vlan))
373372
return ofmsgs
374373

375374
def _get_all_configured_port_nos(self):
@@ -403,7 +402,7 @@ def _add_ports_and_vlans(self, discovered_up_port_nos):
403402
ofmsgs = []
404403
ofmsgs.extend(self.ports_add(
405404
all_up_port_nos, cold_start=True, log_msg='configured'))
406-
ofmsgs.extend(self._add_vlans(self.dp.vlans.values()))
405+
ofmsgs.extend(self.add_vlans(self.dp.vlans.values()))
407406
return ofmsgs
408407

409408
def ofdescstats_handler(self, body):
@@ -612,8 +611,7 @@ def _update_stack_link_state(self, ports, now, other_valves):
612611
if not valve.dp.dyn_running:
613612
continue
614613
ofmsgs_by_valve[valve].extend(valve.get_tunnel_flowmods())
615-
for vlan in valve.dp.vlans.values():
616-
ofmsgs_by_valve[valve].extend(valve.flood_manager.add_vlan(vlan))
614+
ofmsgs_by_valve[valve].extend(valve.add_vlans(valve.dp.vlans.values()))
617615
for port in valve.dp.stack_ports:
618616
ofmsgs_by_valve[valve].extend(valve.host_manager.del_port(port))
619617
path_port = valve.dp.shortest_path_port(valve.dp.stack_root_name)
@@ -871,8 +869,7 @@ def ports_add(self, port_nums, cold_start=False, log_msg='up'):
871869

872870
# Only update flooding rules if not cold starting.
873871
if not cold_start:
874-
for vlan in vlans_with_ports_added:
875-
ofmsgs.extend(self.flood_manager.add_vlan(vlan))
872+
ofmsgs.extend(self.add_vlans(vlans_with_ports_added))
876873
return ofmsgs
877874

878875
def port_add(self, port_num):
@@ -947,8 +944,7 @@ def lacp_down(self, port, cold_start=False, lacp_pkt=None):
947944
port.lacp_update(False, lacp_pkt=lacp_pkt)
948945
if not cold_start:
949946
ofmsgs.extend(self.host_manager.del_port(port))
950-
for vlan in port.vlans():
951-
ofmsgs.extend(self.flood_manager.add_vlan(vlan))
947+
ofmsgs.extend(self.add_vlans(port.vlans()))
952948
vlan_table = self.dp.tables['vlan']
953949
ofmsgs.append(vlan_table.flowdrop(
954950
match=vlan_table.match(in_port=port.number),
@@ -978,8 +974,7 @@ def lacp_up(self, port, now, lacp_pkt):
978974
ofmsgs.append(vlan_table.flowdel(
979975
match=vlan_table.match(in_port=port.number),
980976
priority=self.dp.high_priority, strict=True))
981-
for vlan in port.vlans():
982-
ofmsgs.extend(self.flood_manager.add_vlan(vlan))
977+
ofmsgs.extend(self.add_vlans(port.vlans()))
983978
self._reset_lacp_status(port)
984979
return ofmsgs
985980

@@ -1644,7 +1639,7 @@ def _apply_config_changes(self, new_dp, changes):
16441639
ofmsgs.extend(self.ports_delete(deleted_ports))
16451640
if deleted_vids:
16461641
deleted_vlans = [self.dp.vlans[vid] for vid in deleted_vids]
1647-
ofmsgs.extend(self._del_vlans(deleted_vlans))
1642+
ofmsgs.extend(self.del_vlans(deleted_vlans))
16481643
if changed_ports:
16491644
ofmsgs.extend(self.ports_delete(changed_ports))
16501645

@@ -1653,8 +1648,10 @@ def _apply_config_changes(self, new_dp, changes):
16531648
if changed_vids:
16541649
changed_vlans = [self.dp.vlans[vid] for vid in changed_vids]
16551650
# TODO: handle change versus add separately so can avoid delete first.
1656-
ofmsgs.extend(self._del_vlans(changed_vlans))
1657-
ofmsgs.extend(self._add_vlans(changed_vlans))
1651+
ofmsgs.extend(self.del_vlans(changed_vlans))
1652+
for vlan in changed_vlans:
1653+
vlan.reset_caches()
1654+
ofmsgs.extend(self.add_vlans(changed_vlans))
16581655
if changed_ports:
16591656
ofmsgs.extend(self.ports_add(all_up_port_nos))
16601657
if self.acl_manager and changed_acl_ports:

faucet/valve_flood.py

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,16 @@ def __init__(self, logger, flood_table, pipeline, # pylint: disable=too-many-arg
375375
self.external_root_only = True
376376
self._reset_peer_distances()
377377

378+
def _build_flood_acts_for_port(self, vlan, exclude_unicast, port, # pylint: disable=too-many-arguments
379+
exclude_all_external=False,
380+
exclude_restricted_bcast_arpnd=False):
381+
if self.external_root_only:
382+
exclude_all_external = True
383+
return super(ValveFloodStackManagerBase, self)._build_flood_acts_for_port(
384+
vlan, exclude_unicast, port,
385+
exclude_all_external=exclude_all_external,
386+
exclude_restricted_bcast_arpnd=exclude_restricted_bcast_arpnd)
387+
378388
def _flood_actions(self, in_port, external_ports,
379389
away_flood_actions, toward_flood_actions, local_flood_actions):
380390
raise NotImplementedError
@@ -521,8 +531,6 @@ def _build_mask_flood_rules(self, vlan, eth_type, eth_dst, eth_dst_mask, # pyli
521531
for ext_port_flag, exclude_all_external in (
522532
(valve_of.PCP_NONEXT_PORT_FLAG, True),
523533
(valve_of.PCP_EXT_PORT_FLAG, False)):
524-
if self.external_root_only:
525-
exclude_all_external = True
526534
if not prune:
527535
flood_acts, _, _ = self._build_flood_acts_for_port(
528536
vlan, exclude_unicast, port,
@@ -589,12 +597,22 @@ def edge_learn_port(self, other_valves, pkt_meta):
589597
Returns:
590598
port to learn host on, or None.
591599
"""
600+
# Got a packet from another DP.
592601
if pkt_meta.port.stack:
593602
edge_dp = self._edge_dp_for_host(other_valves, pkt_meta)
594-
# No edge DP may have learned this host yet.
595-
if edge_dp is None:
596-
return None
597-
return self.shortest_path_port(edge_dp.name)
603+
if edge_dp:
604+
return self.shortest_path_port(edge_dp.name)
605+
# Assuming no DP has learned this host.
606+
return None
607+
608+
# Got a packet locally.
609+
# If learning on an external port, check another DP hasn't
610+
# already learned on a local/non-external port.
611+
if pkt_meta.port.loop_protect_external:
612+
edge_dp = self._non_stack_learned(other_valves, pkt_meta)
613+
if edge_dp:
614+
return self.shortest_path_port(edge_dp.name)
615+
# Locally learn.
598616
return super(ValveFloodStackManagerBase, self).edge_learn_port(
599617
other_valves, pkt_meta)
600618

@@ -609,6 +627,33 @@ def _edge_dp_for_host(self, other_valves, pkt_meta):
609627
"""
610628
raise NotImplementedError
611629

630+
def _non_stack_learned(self, other_valves, pkt_meta):
631+
other_local_dp_entries = []
632+
other_external_dp_entries = []
633+
vlan_vid = pkt_meta.vlan.vid
634+
for other_valve in other_valves:
635+
other_dp_vlan = other_valve.dp.vlans.get(vlan_vid, None)
636+
if other_dp_vlan is not None:
637+
entry = other_dp_vlan.cached_host(pkt_meta.eth_src)
638+
if not entry:
639+
continue
640+
if entry.port.stack:
641+
continue
642+
if entry.port.loop_protect_external:
643+
other_external_dp_entries.append(other_valve.dp)
644+
else:
645+
other_local_dp_entries.append(other_valve.dp)
646+
# Another DP has learned locally, has priority.
647+
if other_local_dp_entries:
648+
return other_local_dp_entries[0]
649+
# No other DP has learned locally, but at least one has learned externally.
650+
if other_external_dp_entries:
651+
entry = pkt_meta.vlan.cached_host(pkt_meta.eth_src)
652+
# This DP has not learned the host either, use other's external.
653+
if entry is None:
654+
return other_external_dp_entries[0]
655+
return None
656+
612657

613658
class ValveFloodStackManagerNoReflection(ValveFloodStackManagerBase):
614659
"""Stacks of size 2 - all switches directly connected to root.
@@ -635,7 +680,11 @@ def _flood_actions(self, in_port, external_ports,
635680

636681
def _edge_dp_for_host(self, other_valves, pkt_meta):
637682
"""Size 2 means root shortest path is always directly connected."""
638-
return pkt_meta.port.stack['dp']
683+
peer_dp = pkt_meta.port.stack['dp']
684+
if peer_dp.dyn_running:
685+
return self._non_stack_learned(other_valves, pkt_meta)
686+
# Fall back to assuming peer knows if we are not the peer's controller.
687+
return peer_dp
639688

640689

641690
class ValveFloodStackManagerReflection(ValveFloodStackManagerBase):
@@ -757,14 +806,12 @@ def _edge_dp_for_host(self, other_valves, pkt_meta):
757806
# (for example, just default switch to a neighbor).
758807
# Find port that forwards closer to destination DP that
759808
# has already learned this host (if any).
760-
vlan_vid = pkt_meta.vlan.vid
761-
for other_valve in other_valves:
762-
other_dp_vlan = other_valve.dp.vlans.get(vlan_vid, None)
763-
if other_dp_vlan is not None:
764-
entry = other_dp_vlan.cached_host(pkt_meta.eth_src)
765-
if entry and not entry.port.stack:
766-
return other_valve.dp
767809
peer_dp = pkt_meta.port.stack['dp']
768-
if peer_dp.is_stack_edge() or peer_dp.is_stack_root():
769-
return peer_dp
810+
if peer_dp.dyn_running:
811+
return self._non_stack_learned(other_valves, pkt_meta)
812+
else:
813+
# Fall back to peer knows if edge or root if we are not the peer's controller.
814+
if peer_dp.is_stack_edge() or peer_dp.is_stack_root():
815+
return peer_dp
816+
# No DP has learned this host, yet. Take no action to allow remote learning to occur.
770817
return None

faucet/valve_host.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,12 @@ def del_port(self, port):
113113
vlan.clear_cache_hosts_on_port(port)
114114
return ofmsgs
115115

116-
def initialise_tables(self):
116+
def add_vlan(self, vlan):
117117
ofmsgs = []
118-
for vlan in self.vlans.values():
119-
ofmsgs.append(self.eth_src_table.flowcontroller(
120-
match=self.eth_src_table.match(vlan=vlan),
121-
priority=self.low_priority,
122-
inst=[self.eth_src_table.goto(self.output_table)]))
118+
ofmsgs.append(self.eth_src_table.flowcontroller(
119+
match=self.eth_src_table.match(vlan=vlan),
120+
priority=self.low_priority,
121+
inst=[self.eth_src_table.goto(self.output_table)]))
123122
return ofmsgs
124123

125124
def _temp_ban_host_learning(self, match):

tests/integration/mininet_tests.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3063,6 +3063,9 @@ def test_port_change_vlan(self):
30633063
self.change_port_config(
30643064
self.port_map['port_1'], 'native_vlan', 200,
30653065
restart=False, cold_start=False)
3066+
self.wait_until_matching_flow(
3067+
{'vlan_vid': 200}, table_id=self._ETH_SRC_TABLE,
3068+
actions=['OUTPUT:CONTROLLER', 'GOTO_TABLE:%u' % self._ETH_DST_TABLE])
30663069
self.change_port_config(
30673070
self.port_map['port_2'], 'native_vlan', 200,
30683071
restart=True, cold_start=True)
@@ -3087,6 +3090,9 @@ def test_port_change_acl(self):
30873090
{'in_port': int(self.port_map['port_1']),
30883091
'eth_type': IPV4_ETH, 'tcp_dst': 5001, 'ip_proto': 6},
30893092
table_id=self._PORT_ACL_TABLE, cookie=self.ACL_COOKIE)
3093+
self.wait_until_matching_flow(
3094+
{'vlan_vid': 100}, table_id=self._ETH_SRC_TABLE,
3095+
actions=['OUTPUT:CONTROLLER', 'GOTO_TABLE:%u' % self._ETH_DST_TABLE])
30903096
self.verify_tp_dst_blocked(5001, first_host, second_host)
30913097
self.verify_tp_dst_notblocked(5002, first_host, second_host)
30923098
self.reload_conf(
@@ -7029,14 +7035,13 @@ def verify_protected_connectivity(self):
70297035
self.verify_one_broadcast(int_host, ext_hosts)
70307036

70317037
for ext_host in ext_hosts:
7032-
# All external hosts cannot flood to each other.
7033-
for other_ext_host in ext_hosts - {ext_host}:
7034-
self.verify_broadcast(hosts=(ext_host, other_ext_host), broadcast_expected=False)
7035-
70367038
# All external hosts can reach internal hosts.
70377039
for int_host in int_hosts:
70387040
self.verify_broadcast(hosts=(ext_host, int_host), broadcast_expected=True)
70397041
self.one_ipv4_ping(ext_host, int_host.IP())
7042+
# All external hosts cannot flood to each other.
7043+
for other_ext_host in ext_hosts - {ext_host}:
7044+
self.verify_broadcast(hosts=(ext_host, other_ext_host), broadcast_expected=False)
70407045

70417046
def set_externals_state(self, dp_name, externals_up):
70427047
"""Set the port up/down state of all external ports on a switch"""

0 commit comments

Comments
 (0)