Repository: sonic-net/sonic-mgmt

Issue:
Router-MAC should not be Learnt.
Issue#

From SONiC side expected behavior is that we should not do L2 Learn of Source Mac if it is MY-Mac/Self-Router-MAC.
And in case even if it get learnt it should get Age Out eventually if there is no traffic with Router-MAC as Source Address.

Add a test case to send packet with Router-Mac as SA and check it should not learnt and if learnt should get age out eventually.



Discussion:
How to define MY-Mac/Self-Router-MAC ? Is it MAC of vlan or physical interface?
And how do we understand `in case even if it get learnt it should get Age Out eventually` ? The learnt MAC will expire even it's not MY-MAC.

Resolving patch:
diff --git a/ansible/library/fdb_facts.py b/ansible/library/fdb_facts.py
new file mode 100644
--- /dev/null
+++ b/ansible/library/fdb_facts.py
@@ -0,0 +1,79 @@
+#!/usr/bin/python
+
+DOCUMENTATION = '''
+module:         fdb_facts
+version_added:  "1.0"
+short_description: Retrieve fdb from DUT by 'show mac' command
+description:
+    - Retrieve fdb from DUT by 'show mac' command
+    - The retrieved MAC will be returned as a dict
+'''
+
+EXAMPLES = '''
+- name: Get fdb facts from DUT
+  fdb_facts:
+'''
+
+# Example of the output
+'''
+The input:
+$ fdbshow 
+  No.    Vlan  MacAddress         Port        Type
+-----  ------  -----------------  ----------  -------
+    1    1000  24:8A:07:4C:F5:06  Ethernet24  Dynamic
+
+The output:
+{
+    '24:8A:07:4C:F5:06': 
+        {
+            'vlan': '1000',
+            'type': 'Dynamic',
+            'port': 'Ethernet24'
+        }
+}
+
+'''
+
+class FdbModule(object):
+    def __init__(self):
+        self.module = AnsibleModule(argument_spec=dict())
+
+    def run(self):
+        """
+            Main method of the class
+        """
+        cmd = 'show mac'
+        try:
+            rc, out, err = self.module.run_command(cmd, executable='/bin/bash', use_unsafe_shell=True)
+        except Exception as e:
+            self.module.fail_json(msg=str(e))
+
+        if rc != 0:
+            self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" %
+                                      (rc, self.out, err))
+        ret = {}
+        # Parse output of 'show mac'
+        for line in out.split('\n'):
+            d = line.split()
+            if len(d) != 5:
+                continue
+            # Skip if the first column is not a number
+            if not d[0].strip().isdigit():
+                continue
+            mac = d[2].strip()
+            ret[mac] = {
+                    'vlan': int(d[1].strip()) if d[1] != "" else 0,
+                    'port': d[3].strip(),
+                    'type': d[4].strip()
+                }
+
+        self.module.exit_json(ansible_facts=ret)
+
+def main():
+    bgp = FdbModule()
+    bgp.run()
+
+from ansible.module_utils.basic import *
+if __name__ == "__main__":
+    main()
+


Test patch:
diff --git a/tests/fdb/test_fdb.py b/tests/fdb/test_fdb.py
--- a/tests/fdb/test_fdb.py
+++ b/tests/fdb/test_fdb.py
@@ -9,6 +9,7 @@
 import logging
 import pprint
 import re
+import random
 
 from tests.common.fixtures.ptfhost_utils import change_mac_addresses      # lgtm[py/unused-import]
 from tests.common.fixtures.ptfhost_utils import remove_ip_addresses       # lgtm[py/unused-import]
@@ -215,6 +216,7 @@ def setup_fdb(ptfadapter, vlan_table, router_mac, pkt_type, dummy_mac_count):
 
     return fdb
 
+
 def validate_mac(mac):
     if mac.find(':') != -1:
         pattern = re.compile(r"^([0-9a-fA-F]{2,2}:){5,5}[0-9a-fA-F]{2,2}$")
@@ -308,33 +310,52 @@ def test_fdb(ansible_adhoc, ptfadapter, duthosts, rand_one_dut_hostname, ptfhost
                 send_recv_eth(ptfadapter, src_ports, src_mac, dst_ports, dst_mac, src_vlan, dst_vlan)
 
     # Should we have fdb_facts ansible module for this test?
-    res = duthost.command('show mac')
-    logger.info('"show mac" output on DUT:\n{}'.format(pprint.pformat(res['stdout_lines'])))
+    fdb_fact = duthost.fdb_facts()['ansible_facts']
+    logger.info('fdb facts on DUT:\n{}'.format(pprint.pformat(fdb_fact)))
 
     dummy_mac_count = 0
     total_mac_count = 0
-    for l in res['stdout_lines']:
-        # No. Vlan MacAddress Port Type
-        items = l.split()
-        if len(items) != 5:
-            continue
-        # First item must be number
-        if not items[0].isdigit():
-            continue
-        vlan_id = int(items[1])
-        mac = items[2]
-        ifname = items[3]
-        fdb_type = items[4]
-        assert ifname in interface_table
-        assert vlan_id in interface_table[ifname]
-        assert validate_mac(mac) == True
-        assert fdb_type in ['Dynamic', 'Static']
-        if DUMMY_MAC_PREFIX in l.lower():
+    for k, v in fdb_fact.items():
+        assert v['port'] in interface_table
+        assert v['vlan'] in interface_table[ifname]
+        assert validate_mac(k) == True
+        assert v['type'] in ['Dynamic', 'Static']
+        if DUMMY_MAC_PREFIX in k.lower():
             dummy_mac_count += 1
-        if "dynamic" in l.lower():
+        if "dynamic" in k.lower():
             total_mac_count += 1
 
     assert vlan_member_count > 0
 
     # Verify that the number of dummy MAC entries is expected
     assert dummy_mac_count == configured_dummay_mac_count * vlan_member_count
+
+
+@pytest.mark.parametrize("pkt_type", PKT_TYPES)
+def test_self_mac_not_learnt(ptfadapter, rand_selected_dut, pkt_type, toggle_all_simulator_ports_to_rand_selected_tor_m, tbinfo):
+    """
+    Verify self mac will not be learnt.
+    """
+    # Clear existing FDB entry from DUT
+    rand_selected_dut.command('sonic-clear fdb all')
+    # Sleep some time to ensure clear is done
+    time.sleep(5)
+    if pkt_type == "cleanup":
+        return
+
+    mg_facts = rand_selected_dut.get_extended_minigraph_facts(tbinfo)
+    port_index = random.choice(list(mg_facts['minigraph_ptf_indices'].values()))
+    self_mac = rand_selected_dut.facts['router_mac']
+    dummy_mac = "00:22:33:44:33:22"
+    if pkt_type == "ethernet":
+        send_eth(ptfadapter, port_index, self_mac, dummy_mac, 0)
+    elif pkt_type == "arp_request":
+        send_arp_request(ptfadapter, port_index, self_mac, dummy_mac, 0)
+    elif pkt_type == "arp_reply":
+        send_arp_reply(ptfadapter, port_index, self_mac, dummy_mac, 0)
+    # Sleep some time to ensure FDB is populate
+    time.sleep(5)
+    # Verify that self mac is not learnt
+    fdb_facts = rand_selected_dut.fdb_facts()['ansible_facts']
+    pytest_assert(self_mac not in fdb_facts,
+                    "Self-mac {} is not supposed to be learnt".format(self_mac))