From 1b9c295e7fa4f8a05dec87252f8857fba8478509 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Tue, 8 Dec 2020 23:20:50 -0600 Subject: ietf-ip: Initial enablement of write support Here be dragons, for now. --- doc/roadmap.rst | 20 ++-- ncserver/module/interfaces.py | 6 +- ncserver/module/ip.py | 206 ++++++++++++++++++++++++++++++++++++++++-- ncserver/util.py | 2 +- 4 files changed, 213 insertions(+), 21 deletions(-) diff --git a/doc/roadmap.rst b/doc/roadmap.rst index 1fe6a25..c75584f 100644 --- a/doc/roadmap.rst +++ b/doc/roadmap.rst @@ -292,25 +292,25 @@ Resolved TBDs * [ ] Enable/disable IPv4 on an interface - * [/] Enable/disable IPv4 forwarding on an interface + * [x] Enable/disable IPv4 forwarding on an interface - * [/] Set IPv4 MTU + * [x] Set IPv4 MTU * [ ] Retrieve/set IPv4 address(es) * [ ] Handle neighbour / static ARP cache (set) - * [/] Enable/disable IPv6 on an interface + * [x] Enable/disable IPv6 on an interface - * [/] Enable/disable IPv6 forwarding on an interface + * [x] Enable/disable IPv6 forwarding on an interface - * [/] Set IPv6 MTU + * [x] Set IPv6 MTU * [ ] Retrieve/set IPv6 address(es) - * [/] IPv6 DAD: Neighbour-Solicitation messages sent + * [x] IPv6 DAD: Neighbour-Solicitation messages sent - * [/] SLAAC configuration + * [x] SLAAC configuration * [ ] State nodes @@ -327,14 +327,14 @@ system network interfaces. Outstanding TBDs ```````````````` -* Do we support IPv6 privacy extensions? - * Do we have a way to determine what addresses are static vs DHCP? Can i.e. dhcpcd tell us what addresses it configured? Resolved TBDs ````````````` -* None. +* Do we support IPv6 privacy extensions? + + **Resolution**: Yes. diff --git a/ncserver/module/interfaces.py b/ncserver/module/interfaces.py index d760e0b..7f4fa6e 100644 --- a/ncserver/module/interfaces.py +++ b/ncserver/module/interfaces.py @@ -271,7 +271,7 @@ def _edit_enabled(session, rpc, node, def_op, iface: str): nmsa.set_param(iface, 'enabled', enable) -def edit(session, rpc, node, def_op): +def edit(session, rpc, node, def_op): # pylint: disable=R0912 """Edit the interface configuration for this device.""" methods = {'description': _edit_description, 'enabled': _edit_enabled} @@ -288,8 +288,8 @@ def edit(session, rpc, node, def_op): name_node = interface.find('{'+M_NS+'}name') if name_node is None: raise error.MissingElementAppError(rpc, interface) + iface = name_node.text - interface.remove(name_node) operation = node_operation(interface, def_op) if operation in ('create', 'delete', 'remove'): @@ -316,5 +316,7 @@ def edit(session, rpc, node, def_op): name = QName(candidate.tag).localname if name in methods: methods[name](session, rpc, candidate, operation, iface) + elif name == 'name': + continue else: maybe_raise_on_invalid_node(M_NS, rpc, candidate) diff --git a/ncserver/module/ip.py b/ncserver/module/ip.py index cd4cde0..8329c81 100644 --- a/ncserver/module/ip.py +++ b/ncserver/module/ip.py @@ -13,9 +13,10 @@ SPDX-License-Identifier: NCSA import logging from lxml.etree import QName # pylint: disable=E0611 -from netconf import util +from netconf import error, util -from ncserver.base.util import _ +from ncserver.base.log import log_config_change +from ncserver.base.util import _, node_operation from ncserver.util import get_nmsa @@ -99,18 +100,25 @@ def _add_ipv4(iface, getter): def _add_ipv6(iface, getter): """Add IPv6 configuration nodes.""" name = iface.find('{'+IF_NS+'}name').text + if getter(name, 'ipv6_enabled') is None: + return None # Unset means we don't have IPv6 config at all. + ipv6 = util.subelm(iface, 'ip:ipv6') ipv6.append(util.leaf_elm('ip:enabled', from_bool(getter(name, 'ipv6_enabled')))) - ipv6.append(util.leaf_elm('ip:forwarding', - from_bool(getter(name, 'ipv6_forwarding')))) + + forwarding = getter(name, 'ipv6_forwarding') + if forwarding is not None: + ipv6.append(util.leaf_elm('ip:forwarding', from_bool(forwarding))) + v6mtu = getter(name, 'ipv6_mtu') if v6mtu is not None: ipv6.append(util.leaf_elm('ip:mtu', v6mtu)) - ipv6.append( - util.leaf_elm('ip:dup-addr-detect-transmits', - getter(name, 'ipv6_dad_xmit')) - ) + + dad_xmit = getter(name, 'ipv6_dad_xmit') + if dad_xmit is not None: + ipv6.append(util.leaf_elm('ip:dup-addr-detect-transmits', dad_xmit)) + if any((getter(name, 'ipv6_slaac_globaladdr'), getter(name, 'ipv6_slaac_tempaddr'))): autoconf = util.subelm(ipv6, 'ip:autoconf') @@ -159,6 +167,9 @@ def running(node): # IPv6 ipv6 = _add_ipv6(iface, nmsa.get_param) + if not ipv6: + continue + for address in nmsa.list_addresses(name): # Only IPv6 addesses count. if ':' not in address: @@ -189,5 +200,184 @@ def operational(node): _add_ipv6(iface, nmsa.curr_param) +def _edit_param(iface: str, param: str, operation: str, rpc, node): + """Edit an NMSA-controlled parameter.""" + nmsa = get_nmsa() + value = nmsa.get_param(iface, param) + + if operation == 'create' and value is not None: + raise error.DataExistsAppError(rpc) + if operation == 'delete' and value is None: + raise error.DataMissingAppError(rpc) + + if operation in ('delete', 'remove'): + nmsa.unset_param(iface, param) + return + + if operation not in ('create', 'merge', 'replace'): + raise error.OperationNotSupportedAppError(rpc) + + if node.text in ('true', 'false'): + nmsa.set_param(iface, param, node.text == 'true') + else: + nmsa.set_param(iface, param, node.text) + + +def _clear_ipv4(iface: str): + """Remove all IPv4 configuration from a given interface.""" + nmsa = get_nmsa() + + nmsa.set_param(iface, 'ipv4_enabled', False) + nmsa.unset_param(iface, 'ipv4_forwarding') + nmsa.unset_param(iface, 'ipv4_mtu') + + +def _edit_ipv4(session, rpc, node, def_op, iface: str): + """Edit IPv4 configuration for a given interface.""" + _params = {'enabled': 'ipv4_enabled', 'forwarding': 'ipv4_forwarding', + 'mtu': 'ipv4_mtu'} + + operation = node_operation(node, def_op) + if operation in ('delete', 'remove'): + log_config_change(session, "[ietf-ip %s]" % iface, + "removing IPv4 configuration") + + _clear_ipv4(iface) + return + + if operation not in ('create', 'merge', 'replace'): + raise error.OperationNotSupportedAppError(rpc) + + for xparam in node: + operation = node_operation(xparam, operation) + qparam = QName(xparam.tag) + if qparam.localname in _params.keys(): + param = _params[qparam.localname] + log_config_change(session, "[ietf-ip %s]" % iface, + "IPv4 %s: -> %s" % (param, node.text)) + _edit_param(iface, param, operation, rpc, node) + elif qparam.localname == 'address': + # Oh no. + raise NotImplementedError + elif qparam.localname == 'neighbor': + # Oh *no*! + raise NotImplementedError + else: + raise error.UnknownElementAppError(rpc, xparam) + + +def _clear_ipv6(iface: str): + """Remove all IPv6 configuration from a given interface.""" + nmsa = get_nmsa() + + nmsa.set_param(iface, 'ipv6_enabled', False) + for param in ('ipv6_forwarding', 'ipv6_mtu', 'ipv6_dad_xmit', + 'ipv6_slaac_globaladdr', 'ipv6_slaac_tempaddr', + 'ipv6_slaac_validlft', 'ipv6_slaac_preflft'): + nmsa.unset_param(iface, param) + + +def _clear_slaac(iface: str): + """Remove all SLAAC configuration from a given interface.""" + nmsa = get_nmsa() + + for param in ('ipv6_slaac_globaladdr', 'ipv6_slaac_tempaddr', + 'ipv6_slaac_validlft', 'ipv6_slaac_preflft'): + nmsa.unset_param(iface, param) + + +def _edit_slaac(session, rpc, node, def_op, iface: str): + """Edit SLAAC configuration for a given interface.""" + _params = {'create-global-addresses': 'ipv6_slaac_globaladdr', + 'create-temporary-addresses': 'ipv6_slaac_tempaddr', + 'temporary-valid-lifetime': 'ipv6_slaac_validlft', + 'temporary-preferred-lifetime': 'ipv6_slaac_preflft'} + + operation = node_operation(node, def_op) + if operation in ('delete', 'remove'): + log_config_change(session, "[ietf-ip %s]" % iface, + "removing IPv6 SLAAC configuration") + _clear_slaac(iface) + return + + if operation not in ('create', 'merge', 'replace'): + raise error.OperationNotSupportedAppError(rpc) + + for param in node: + p_name = QName(param.tag).localname + if p_name not in _params.keys(): + raise error.UnknownElementAppError(rpc, param) + + p_op = node_operation(param, operation) + _edit_param(iface, _params[p_name], p_op, rpc, param) + + +def _edit_ipv6(session, rpc, node, def_op, iface: str): + """Edit IPv6 configuration for a given interface.""" + _params = {'enabled': 'ipv6_enabled', 'forwarding': 'ipv6_forwarding', + 'mtu': 'ipv6_mtu', 'dup-addr-detect-transmits': 'ipv6_dad_xmit'} + + operation = node_operation(node, def_op) + if operation in ('delete', 'remove'): + log_config_change(session, "[ietf-ip %s]" % iface, + "removing IPv6 configuration") + + _clear_ipv6(iface) + return + + if operation not in ('create', 'merge', 'replace'): + raise error.OperationNotSupportedAppError(rpc) + + for xparam in node: + p_op = node_operation(xparam, operation) + qparam = QName(xparam.tag) + if qparam.localname in _params.keys(): + param = _params[qparam.localname] + log_config_change(session, "[ietf-ip %s]" % iface, + "IPv6 %s: -> %s" % (param, node.text)) + _edit_param(iface, param, p_op, rpc, node) + elif qparam.localname == 'address': + # Oh no. + raise NotImplementedError + elif qparam.localname == 'neighbor': + # Oh *no*! + raise NotImplementedError + elif qparam.localname == 'autoconf': + # Configure SLAAC parameters. + _edit_slaac(session, rpc, node, def_op, iface) + else: + raise error.UnknownElementAppError(rpc, xparam) + + def edit(session, rpc, node, def_op): """Edit the IP configuration for this device.""" + methods = {'ipv4': _edit_ipv4, 'ipv6': _edit_ipv6} + + nmsa = get_nmsa() + if nmsa is None: + raise error.OperationNotSupportedAppError(rpc) + + for interface in node: + if QName(interface.tag).localname != 'interface' or\ + QName(interface.tag).namespace != IF_NS: + continue # Ignore unknown tags given to us. + + name_node = interface.find('{'+IF_NS+'}name') + if name_node is None: + raise error.MissingElementAppError(rpc, interface) + iface = name_node.text + + operation = node_operation(interface, def_op) + if operation in ('delete', 'remove'): + # ietf-interfaces already removed the configuration for us. + continue + + for conf in interface: + if QName(conf.tag).namespace == M_NS: + name = QName(conf.tag).localname + if name in methods: + methods[name](session, rpc, conf, operation, iface) + else: + raise error.UnknownElementAppError(rpc, conf) + else: + continue diff --git a/ncserver/util.py b/ncserver/util.py index 229fbf1..3897d2c 100644 --- a/ncserver/util.py +++ b/ncserver/util.py @@ -37,7 +37,7 @@ def maybe_raise_on_invalid_node(xmlns, rpc, node): if nodens == xmlns: raise error.UnknownElementAppError(rpc, node) # pylint: disable=W0212 - if nodens not in (mod.M_PREFIX for mod in MODMAN._augments_for_ns(xmlns)): + if nodens not in (mod.M_NS for mod in MODMAN._augments_for_ns(xmlns)): raise error.UnknownNamespaceAppError(rpc, node, None) -- cgit v1.2.3-60-g2f50