summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA. Wilcox <AWilcox@Wilcox-Tech.com>2020-11-20 13:54:13 -0600
committerA. Wilcox <AWilcox@Wilcox-Tech.com>2020-11-20 13:54:13 -0600
commit99dd6783e3c2c19f84ea0a44703c003cb0287132 (patch)
treead363a0829ca5e51b953df7e034efcd17055317a
parent79444d36e07d2e43697fe6b35d8870a371062690 (diff)
downloadnetconfapk-99dd6783e3c2c19f84ea0a44703c003cb0287132.tar.gz
netconfapk-99dd6783e3c2c19f84ea0a44703c003cb0287132.tar.bz2
netconfapk-99dd6783e3c2c19f84ea0a44703c003cb0287132.tar.xz
netconfapk-99dd6783e3c2c19f84ea0a44703c003cb0287132.zip
ifupdown-ng NMSA: Add sysctl support
-rw-r--r--ncserver/module/nms_ifupdownng.py148
1 files changed, 140 insertions, 8 deletions
diff --git a/ncserver/module/nms_ifupdownng.py b/ncserver/module/nms_ifupdownng.py
index f844d32..5f87e8c 100644
--- a/ncserver/module/nms_ifupdownng.py
+++ b/ncserver/module/nms_ifupdownng.py
@@ -48,6 +48,10 @@ _CONFIG = dict()
"""The internal configuration handle."""
+_SYSCTL = dict()
+"""The internatl sysctl configuration handle."""
+
+
_TRANSACTION = False
"""Determines if a transaction is in progress."""
@@ -105,6 +109,17 @@ def _save_unlocked():
conf_file.write(eni[:-1])
+def _sysctl_save_unlocked():
+ """Save changes to sysctl.conf (and sysctl itself)."""
+ buf = "\n".join("{key}={val}".format(key=key, val=val)
+ for key, val in _SYSCTL.items())
+
+ with open('/etc/netconf/sysctl.conf', 'w') as conf_file:
+ conf_file.write(buf)
+
+ subprocess.run(['sysctl', '-f', '/etc/netconf/sysctl.conf'], check=False)
+
+
def _save():
"""Save configuration changes, if a transaction is not in progress."""
if _TRANSACTION:
@@ -113,6 +128,14 @@ def _save():
_save_unlocked()
+def _sysctl_save():
+ """Save sysctl changes, if a transaction is not in progress."""
+ if _TRANSACTION:
+ return
+
+ _sysctl_save_unlocked()
+
+
def _find_one(iface: str, key: str):
"""Find a single instance of configuration +key+ for +iface+."""
if iface not in _CONFIG.keys():
@@ -165,7 +188,30 @@ def _remove_one(iface: str, key: str):
def _iface_path(iface: str) -> pathlib.Path:
"""Retrieve the system device path for the specified interface."""
- return pathlib.Path("/sys/class/net/" + iface)
+ return pathlib.Path('/sys/class/net/' + iface)
+
+
+def _load_sysctl_cfg() -> dict:
+ """Retrieve the desired sysctl configuration."""
+ global _SYSCTL # pylint: disable=W0603
+
+ # Won't load during a transaction.
+ if _TRANSACTION:
+ return
+
+ sysctl_path = pathlib.Path('/etc/netconf/sysctl.conf')
+ if not sysctl_path.exists():
+ LOGGER.error(_("NETCONF sysctl config missing; check installation"))
+ return
+
+ lines = list()
+ with open(sysctl_path, 'r') as sysctl_file:
+ lines = sysctl_file.readlines()
+
+ _SYSCTL = dict({
+ value[0]: value[1].strip() for value in
+ [line.split('=') for line in lines]
+ })
#############################
@@ -177,19 +223,58 @@ _ENI_MAPPING = {'description': 'netconf-description',
"""Mapping of NMSA keys to /e/n/i keys."""
+_SYSCTL_MAPPING = {
+ 'ipv4_forwarding': 'net.ipv4.conf.{_if}.forwarding',
+ 'ipv6_forwarding': 'net.ipv6.conf.{_if}.forwarding',
+ 'ipv6_mtu': 'net.ipv6.conf.{_if}.mtu',
+ 'ipv6_dad_xmit': 'net.ipv6.conf.{_if}.dad_transmits',
+ 'ipv6_slaac_validlft': 'net.ipv6.conf.{_if}.temp_valid_lft',
+ 'ipv6_slaac_preflft': 'net.ipv6.conf.{_if}.temp_prefered_lft'
+}
+"""Mapping of NMSA keys to sysctl nodes."""
+
+
+# # # # Getters # # # #
+
def get_one_eni(iface: str, parameter: str):
"""Retrieve the specified parameter from /e/n/i."""
return _find_one(iface, _ENI_MAPPING[parameter])
-def unset_one_eni(iface: str, parameter: str):
- """Unset a parameter in /e/n/i."""
- _remove_one(iface, _ENI_MAPPING[parameter])
+def get_sysctl(iface: str, parameter: str):
+ """Retrieve the specified parameter from sysctl.conf."""
+ key = _SYSCTL_MAPPING[parameter].format(_if=iface)
+ fallback = _SYSCTL_MAPPING[parameter].format(_if='all')
+ return _SYSCTL.get(key, _SYSCTL.get(fallback, None))
-def set_desc(iface: str, _, value: str):
- """Set the description for the specified interface."""
- _replace_one(iface, 'netconf-description', value)
+
+def get_sysctl_bool(iface: str, parameter: str) -> bool:
+ """Retrieve the specified boolean from sysctl.conf."""
+ value = get_sysctl(iface, parameter)
+ if value == '1':
+ return True
+ return False
+
+
+def live_sysctl(iface: str, parameter: str):
+ """Retrieve the live value of a parameter from /proc/sys."""
+ key = _SYSCTL_MAPPING[parameter].format(_if=iface)
+
+ path = pathlib.Path("/proc/sys/" + key.replace('.', '/'))
+ if path.exists():
+ with open(path, 'r') as sysctl:
+ return sysctl.read().strip()
+
+ return None
+
+
+def live_sysctl_bool(iface: str, parameter: str) -> bool:
+ """Retrieve a live boolean value from /proc/sys."""
+ value = live_sysctl(iface, parameter)
+ if value is not None and value == '1':
+ return True
+ return False
def live_enabled(iface: str, _):
@@ -202,15 +287,55 @@ def live_enabled(iface: str, _):
return False
+# # # # Setters # # # #
+
+def set_sysctl(iface: str, parameter: str, value):
+ """Set a sysctl value."""
+ key = _SYSCTL_MAPPING[parameter].format(_if=iface)
+ _SYSCTL[key] = value
+ _sysctl_save()
+
+
+def set_sysctl_bool(iface: str, parameter: str, value: bool):
+ """Set a boolean sysctl value."""
+ real = '0'
+ if value:
+ real = '1'
+
+ set_sysctl(iface, parameter, real)
+
+
+def set_desc(iface: str, _, value: str):
+ """Set the description for the specified interface."""
+ _replace_one(iface, 'netconf-description', value)
+
+
def set_auto(iface: str, _, value: bool):
"""Set the auto flag for the specified interface."""
_replace_one(iface, 'auto', str(value).lower())
+# # # # Unsetters # # # #
+
+def unset_one_eni(iface: str, parameter: str):
+ """Unset a parameter in /e/n/i."""
+ _remove_one(iface, _ENI_MAPPING[parameter])
+
+
+def unset_sysctl(iface: str, parameter: str):
+ """Unset a sysctl."""
+ key = _SYSCTL_MAPPING[parameter].format(_if=iface)
+ _SYSCTL.pop(key, None)
+ _sysctl_save()
+
+
_PARAMETERS = {
# "name": (getter, live getter, setter, unsetter)
"description": (get_one_eni, get_one_eni, set_desc, unset_one_eni),
- "enabled": (get_one_eni, live_enabled, set_auto, unset_one_eni)
+ "enabled": (get_one_eni, live_enabled, set_auto, unset_one_eni),
+ # XXX TODO: ipv4_enabled
+ "ipv4_forwarding": (get_sysctl_bool, live_sysctl_bool,
+ set_sysctl_bool, unset_sysctl)
}
"""Describes all supported parameters and their methods."""
@@ -255,12 +380,15 @@ def commit():
LOGGER.warning(_("commit when no transaction is in progress"))
_save_unlocked()
+ _sysctl_save_unlocked()
+
_TRANSACTION = False
def get_param(iface: str, parameter: str):
"""Retrieve the parameter for the specified interface."""
_load_config()
+ _load_sysctl_cfg()
if iface not in _CONFIG.keys():
LOGGER.warning(
@@ -280,6 +408,7 @@ def get_param(iface: str, parameter: str):
def curr_param(iface: str, parameter: str):
"""Retrieve the current parameter value for the specified interface."""
_load_config()
+ # Won't read from sysctl.conf so don't need to _load_sysctl_cfg.
# XXX how do we want to handle interfaces that are missing from /e/n/i
# but are still present in the system?
@@ -301,6 +430,7 @@ def curr_param(iface: str, parameter: str):
def set_param(iface: str, parameter: str, value):
"""Set the parameter for the specified interface."""
_load_config()
+ _load_sysctl_cfg()
if parameter not in _PARAMETERS.keys():
LOGGER.error(
@@ -319,6 +449,7 @@ def set_param(iface: str, parameter: str, value):
def unset_param(iface: str, parameter: str):
"""Unset the parameter for the specified interface."""
_load_config()
+ _load_sysctl_cfg()
if iface not in _CONFIG.keys():
LOGGER.warning(
@@ -399,3 +530,4 @@ def remove_address(iface: str, addr: str):
# Load immediately when we're loaded so we can go straight to a transaction
# if desired.
_load_config()
+_load_sysctl_cfg()