summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA. Wilcox <AWilcox@Wilcox-Tech.com>2020-12-08 23:20:50 -0600
committerA. Wilcox <AWilcox@Wilcox-Tech.com>2020-12-08 23:20:50 -0600
commit1b9c295e7fa4f8a05dec87252f8857fba8478509 (patch)
tree533ed91a5d2debcc9be022dc74007eec97762dea
parent1380813fea63570f518f04e472c596877ed85c60 (diff)
downloadnetconfapk-1b9c295e7fa4f8a05dec87252f8857fba8478509.tar.gz
netconfapk-1b9c295e7fa4f8a05dec87252f8857fba8478509.tar.bz2
netconfapk-1b9c295e7fa4f8a05dec87252f8857fba8478509.tar.xz
netconfapk-1b9c295e7fa4f8a05dec87252f8857fba8478509.zip
ietf-ip: Initial enablement of write support
Here be dragons, for now.
-rw-r--r--doc/roadmap.rst20
-rw-r--r--ncserver/module/interfaces.py6
-rw-r--r--ncserver/module/ip.py206
-rw-r--r--ncserver/util.py2
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)