From fba3e4b624529576cbfe59f10dfc1bb3df80634f Mon Sep 17 00:00:00 2001
From: Thomas Mangin <thomas.mangin@exa.net.uk>
Date: Tue, 23 Jun 2020 18:24:29 +0100
Subject: validation: T2630: bound to interface mtu if available

---
 python/vyos/ifconfig/control.py | 34 ++++++++++++++++++++++++++++++++--
 python/vyos/validate.py         | 31 ++++++++++++++++++++++++++++---
 2 files changed, 60 insertions(+), 5 deletions(-)

diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py
index 0958be642..a6fc8ac6c 100644
--- a/python/vyos/ifconfig/control.py
+++ b/python/vyos/ifconfig/control.py
@@ -15,6 +15,8 @@
 
 
 import os
+from inspect import signature
+from inspect import _empty
 
 from vyos import debug
 from vyos.util import popen
@@ -25,6 +27,7 @@ from vyos.ifconfig.section import Section
 class Control(Section):
     _command_get = {}
     _command_set = {}
+    _signature = {}
 
     def __init__(self, **kargs):
         # some commands (such as operation comands - show interfaces, etc.) 
@@ -54,6 +57,30 @@ class Control(Section):
         cmd = self._command_get[name]['shellcmd'].format(**config)
         return self._command_get[name].get('format', lambda _: _)(self._cmd(cmd))
 
+    def _values(self, name, validate, value):
+        """
+        looks at the validation function "validate"
+        for the interface sysfs or command and
+        returns a dict with the right options to call it
+        """
+        if name not in self._signature:
+            self._signature[name] = signature(validate)
+
+        values = {}
+
+        for k in self._signature[name].parameters:
+            default = self._signature[name].parameters[k].default
+            if default is not _empty:
+                continue
+            if k == 'self':
+                values[k] = self
+            elif k == 'ifname':
+                values[k] = self.ifname
+            else:
+                values[k] = value
+
+        return values
+
     def _set_command(self, config, name, value):
         """
         Using the defined names, set data write to sysfs.
@@ -64,7 +91,7 @@ class Control(Section):
         validate = self._command_set[name].get('validate', None)
         if validate:
             try:
-                validate(value)
+                validate(**self._values(name, validate, value))
             except Exception as e:
                 raise e.__class__(f'Could not set {name}. {e}')
 
@@ -124,7 +151,10 @@ class Control(Section):
 
         validate = self._sysfs_set[name].get('validate', None)
         if validate:
-            validate(value)
+            try:
+                validate(**self._values(name, validate, value))
+            except Exception as e:
+                raise e.__class__(f'Could not set {name}. {e}')
 
         config = {**config, **{'value': value}}
 
diff --git a/python/vyos/validate.py b/python/vyos/validate.py
index 6304fa8de..9072c5817 100644
--- a/python/vyos/validate.py
+++ b/python/vyos/validate.py
@@ -13,10 +13,23 @@
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 
+import json
 import socket
 import netifaces
 import ipaddress
 
+from vyos.util import cmd
+
+# Important note when you are adding new validation functions:
+#
+# The Control class will analyse the signature of the function in this file
+# and will build the parameters to be passed to it.
+#
+# The parameter names "ifname" and "self" will get the Interface name and class
+# parameters with default will be left unset
+# all other paramters will receive the value to check
+
+
 def is_ip(addr):
     """
     Check addr if it is an IPv4 or IPv6 address
@@ -208,10 +221,21 @@ def assert_positive(n, smaller=0):
         raise ValueError(f'{n} is smaller than {smaller}')
 
 
-def assert_mtu(mtu, min=68, max=9000):
+def assert_mtu(mtu, ifname):
     assert_number(mtu)
-    if int(mtu) < min or int(mtu) > max:
-        raise ValueError(f'Invalid MTU size: "{mtu}"')
+
+    out = cmd(f'ip -j -d link show dev {ifname}')
+    # [{"ifindex":2,"ifname":"eth0","flags":["BROADCAST","MULTICAST","UP","LOWER_UP"],"mtu":1500,"qdisc":"pfifo_fast","operstate":"UP","linkmode":"DEFAULT","group":"default","txqlen":1000,"link_type":"ether","address":"08:00:27:d9:5b:04","broadcast":"ff:ff:ff:ff:ff:ff","promiscuity":0,"min_mtu":46,"max_mtu":16110,"inet6_addr_gen_mode":"none","num_tx_queues":1,"num_rx_queues":1,"gso_max_size":65536,"gso_max_segs":65535}]
+    parsed = json.loads(out)[0]
+    min_mtu = int(parsed.get('min_mtu', '0'))
+    # cur_mtu = parsed.get('mtu',0),
+    max_mtu = int(parsed.get('max_mtu', '0'))
+    cur_mtu = int(mtu)
+
+    if (min_mtu and cur_mtu < min_mtu) or cur_mtu < 68:
+        raise ValueError(f'MTU is too small for interface "{ifname}": {mtu} < {min_mtu}')
+    if (max_mtu and cur_mtu > max_mtu) or cur_mtu > 65536:
+        raise ValueError(f'MTU is too small for interface "{ifname}": {mtu} > {max_mtu}')
 
 
 def assert_mac(m):
@@ -241,6 +265,7 @@ def assert_mac(m):
     if octets[:5] == (0, 0, 94, 0, 1):
         raise ValueError(f'{m} is a VRRP MAC address')
 
+
 def is_member(conf, interface, intftype=None):
     """
     Checks if passed interface is member of other interface of specified type.
-- 
cgit v1.2.3