From aac0a47b6f4556c96744ce8a73c14819494a7cf7 Mon Sep 17 00:00:00 2001
From: erkin <me@erkin.party>
Date: Thu, 19 Jan 2023 18:05:41 +0300
Subject: igmp-proxy: T4912: Switch away from Jinja2 to Tabulate, remove
 undocumented "show MFC" command

---
 op-mode-definitions/show-ip-multicast.xml.in |   6 -
 src/op_mode/igmp-proxy.py                    | 167 ++++-----------------------
 2 files changed, 24 insertions(+), 149 deletions(-)

diff --git a/op-mode-definitions/show-ip-multicast.xml.in b/op-mode-definitions/show-ip-multicast.xml.in
index d8f16d997..605d61e8d 100644
--- a/op-mode-definitions/show-ip-multicast.xml.in
+++ b/op-mode-definitions/show-ip-multicast.xml.in
@@ -15,12 +15,6 @@
                 </properties>
                 <command>${vyos_op_scripts_dir}/igmp-proxy.py show_interface</command>
               </leafNode>
-              <leafNode name="mfc">
-                <properties>
-                  <help>Show multicast fowarding cache</help>
-                </properties>
-                <command>${vyos_op_scripts_dir}/igmp-proxy.py show_mfc</command>
-              </leafNode>
               <leafNode name="summary">
                 <properties>
                   <help>IP multicast information</help>
diff --git a/src/op_mode/igmp-proxy.py b/src/op_mode/igmp-proxy.py
index 2a8f284bc..0086c9aa6 100755
--- a/src/op_mode/igmp-proxy.py
+++ b/src/op_mode/igmp-proxy.py
@@ -21,9 +21,9 @@
 
 import ipaddress
 import json
-import jinja2
 import socket
 import sys
+import tabulate
 
 import vyos.config
 import vyos.opmode
@@ -43,161 +43,42 @@ def _kernel_to_ip(addr):
     Convert any given address from Linux kernel to a proper, IPv4 address
     using the correct host byte order.
     """
-
     # Convert from hex 'FE000A0A' to decimal '4261415434'
     addr = int(addr, 16)
-    # Kernel ABI _always_ uses network byte order
+    # Kernel ABI _always_ uses network byte order.
     addr = socket.ntohl(addr)
-
     return str(ipaddress.IPv4Address(addr))
 
 def _process_mr_vif():
-    """
-    Read contents of file /proc/net/ip_mr_vif and print a more human
-    friendly version to the command line. IPv4 addresses presented as
-    32-bit integers in hex format are converted to IPv4 notation too.
-    """
-
-    with open('/proc/net/ip_mr_vif', 'r') as f:
-        lines = len(f.readlines())
-        if lines < 2:
-            return None
-
-    result = {
-        'data': []
-    }
-
-    # Build up table format string
-    table_format = {
-        'interface': 'Interface',
-        'pkts_in'  : 'PktsIn',
-        'pkts_out' : 'PktsOut',
-        'bytes_in' : 'BytesIn',
-        'bytes_out': 'BytesOut',
-        'loc'      : 'Local'
-    }
-    result['data'].append(table_format)
-
-    # read and parse information from /proc filesystema
+    """Read rows from /proc/net/ip_mr_vif into dicts."""
+    result = []
     with open('/proc/net/ip_mr_vif', 'r') as f:
-        header_line = next(f)
+        next(f)
         for line in f:
-            data = {
-                'interface': line.split()[1],
-                'pkts_in'  : line.split()[3],
-                'pkts_out' : line.split()[5],
-
-                # convert raw byte number to something more human readable
-                'bytes_in' : bytes_to_human(int(line.split()[2])),
-                'bytes_out': bytes_to_human(int(line.split()[4])),
-
-                # convert IP address from hex 'FE000A0A' to decimal '4261415434'
-                'loc'      : _kernel_to_ip(line.split()[7]),
-            }
-            result['data'].append(data)
-
+            result.append({
+                'Interface': line.split()[1],
+                'PktsIn'   : int(line.split()[3]),
+                'PktsOut'  : int(line.split()[5]),
+                'BytesIn'  : int(line.split()[2]),
+                'BytesOut' : int(line.split()[4]),
+                'Local'    : _kernel_to_ip(line.split()[7]),
+            })
     return result
 
-def _process_mr_mfc():
-    """
-    Read contents of file /proc/net/ip_mr_cache and print a more human
-    friendly version to the command line. IPv4 addresses presented as
-    32-bit integers in hex format are converted to IPv4 notation too.
-    """
-
-    with open('/proc/net/ip_mr_cache', 'r') as f:
-        lines = len(f.readlines())
-        if lines < 2:
-            return None
-
-    # We need this to convert from interface index to a real interface name
-    # Thus we also skip the format identifier on list index 0
-    vif = do_mr_vif()['data'][1:]
-
-    result = {
-        'data': []
-    }
-
-    # Build up table format string
-    table_format = {
-        'group' : 'Group',
-        'origin': 'Origin',
-        'iif'   : 'In',
-        'oifs'  : ['Out'],
-        'pkts'  : 'Pkts',
-        'bytes' : 'Bytes',
-        'wrong' : 'Wrong'
-    }
-    result['data'].append(table_format)
-
-    # read and parse information from /proc filesystem
-    with open('/proc/net/ip_mr_cache', 'r') as f:
-        header_line = next(f)
-        for line in f:
-            data = {
-                # convert IP address from hex 'FE000A0A' to decimal '4261415434'
-                'group' : _kernel_to_ip(line.split()[0]),
-                'origin': _kernel_to_ip(line.split()[1]),
-
-                'iif'   : '--',
-                'pkts'  : '',
-                'bytes' : '',
-                'wrong' : '',
-                'oifs'  : []
-            }
-
-            iif = int(line.split()[2])
-            if not ((iif == -1) or (iif == 65535)):
-                data['pkts']  = line.split()[3]
-                data['bytes'] = bytes_to_human(int(line.split()[4]))
-                data['wrong'] = line.split()[5]
-
-                # convert index to real interface name
-                data['iif']  = vif[iif]['interface']
-
-                # convert each output interface index to a real interface name
-                for oif in line.split()[6:]:
-                    idx = int(oif.split(':')[0])
-                    data['oifs'].append(vif[idx]['interface'])
-
-            result['data'].append(data)
-
-    return result
-
-
-# Output template for "show ip multicast interface" command
-#
-# Example:
-# Interface  BytesIn      PktsIn       BytesOut     PktsOut      Local
-# eth0       0.0 B        0            0.0 B        0            xxx.xxx.xxx.65
-# eth1       0.0 B        0            0.0 B        0            xxx.xxx.xx.201
-# eth0.3     0.0 B        0            0.0 B        0            xxx.xxx.x.7
-# tun1       0.0 B        0            0.0 B        0            xxx.xxx.xxx.2
 def show_interface(raw: bool):
-    vif_out_template = """{%- for r in data -%}
-{{ "%-10s"|format(r.interface) }} {{ "%-12s"|format(r.bytes_in) }} {{ "%-12s"|format(r.pkts_in) }} {{ "%-12s"|format(r.bytes_out) }} {{ "%-12s"|format(r.pkts_out) }} {{ "%-15s"|format(r.loc) }}
-{% endfor %}"""
     if data := _process_mr_vif():
         if raw:
-            return json.loads(json.dumps(data))
-        else:
-            return jinja2.Template(vif_out_template).render(data)
-
-# Output template for "show ip multicast mfc" command
-#
-# Example:
-# Group             Origin            In    Out           Pkts        Bytes        Wrong
-# xxx.xxx.xxx.250   xxx.xx.xxx.75     --
-# xxx.xxx.xx.124    xx.xxx.xxx.26     --
-def show_mfc(raw: bool):
-    mfc_out_template = """{%- for r in data -%}
-{{ "%-15s"|format(r.group) }} {{ "%-15s"|format(r.origin) }} {{ "%-12s"|format(r.pkts) }} {{ "%-12s"|format(r.bytes) }} {{ "%-12s"|format(r.wrong) }} {{ "%-10s"|format(r.iif) }} {{ "%-20s"|format(r.oifs|join(', ')) }}
-{% endfor %}"""
-    if data := _process_mr_mfc():
-        if raw:
-            return json.loads(json.dumps(data))
-        else:
-            return jinja2.Template(mfc_out_template).render(data)
+            # Make the interface name the key for each row.
+            table = {}
+            for v in data:
+                table[v.pop('Interface')] = v
+            return json.loads(json.dumps(table))
+        # Make byte values human-readable for the table.
+        arr = []
+        for x in data:
+            arr.append({k: bytes_to_human(v) if k.startswith('Bytes') \
+                        else v for k, v in x.items()})
+        return tabulate.tabulate(arr, headers='keys')
 
 
 if not _is_configured():
-- 
cgit v1.2.3