diff options
author | Christian Poessinger <christian@poessinger.com> | 2020-08-22 23:15:29 +0200 |
---|---|---|
committer | Christian Poessinger <christian@poessinger.com> | 2020-08-22 23:29:11 +0200 |
commit | 98c2c5e0585e0806099a353de207f392223eff9a (patch) | |
tree | 1196c011cb24b099cc73d87d13aa5f9c97f33472 | |
parent | 50a7544ad69cb7b631a499299f9ab32b2e8918a0 (diff) | |
download | vyos-1x-98c2c5e0585e0806099a353de207f392223eff9a.tar.gz vyos-1x-98c2c5e0585e0806099a353de207f392223eff9a.zip |
dhcpv6-pd: T2677: optimize CLI interface for PD configuration
The current CLI did not support multiple prefix-delegations per interface. Some
ISPs only send one /64 to a client per prefix-delegation request, but they
allow the customer to request multiple prefixes.
The 'dhcpv6-options prefix-delegation' node has been renamed and converted to a
tag node named 'dhcpv6-options pd'. The tag node specifies a PD request (>=0).
In the past the user needed to know what prefix will be assigned and required
to calculate the sla-len by himself. The 'sla-len' node was dropped and is now
calculated in the background from the 'dhcpv6-options pd 0 length' node.
It is no longer mandatory to supply the 'sla-id' node, if sla-id is not
specified it is 'guessed' by counting upwards.
Example configuration:
----------------------
ethernet eth1 {
address dhcpv6
dhcpv6-options {
pd 0 {
length 56
interface eth2 {
address 1
}
}
}
}
This will request a /56 assignment from the ISP and will delegate a /64 network
to interface eth2. VyOS will use the interface address ::1 on the delegate
interface (eth2) as its local address.
-rw-r--r-- | data/templates/dhcp-client/ipv6.tmpl | 48 | ||||
-rw-r--r-- | interface-definitions/include/dhcp-options.xml.i | 2 | ||||
-rw-r--r-- | interface-definitions/include/dhcpv6-options.xml.i | 40 | ||||
-rw-r--r-- | python/vyos/configdict.py | 64 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-pppoe.py | 4 | ||||
-rwxr-xr-x | src/migration-scripts/interfaces/11-to-12 | 58 |
6 files changed, 157 insertions, 59 deletions
diff --git a/data/templates/dhcp-client/ipv6.tmpl b/data/templates/dhcp-client/ipv6.tmpl index 9673f302b..e9285d86b 100644 --- a/data/templates/dhcp-client/ipv6.tmpl +++ b/data/templates/dhcp-client/ipv6.tmpl @@ -8,37 +8,43 @@ interface {{ ifname }} { information-only; {% endif %} {% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} - send ia-na 1; # non-temporary address + send ia-na 0; # non-temporary address {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.prefix_delegation is defined %} - send ia-pd 2; # prefix delegation +{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} +{% for pd in dhcpv6_options.pd %} + send ia-pd {{ pd }}; # prefix delegation #{{ pd }} +{% endfor %} {% endif %} }; {% if dhcpv6_options is not defined or dhcpv6_options.temporary is not defined %} -id-assoc na 1 { - # Identity association NA +id-assoc na 0 { + # Identity association for non temporary address }; {% endif %} -{% if dhcpv6_options is defined and dhcpv6_options.prefix_delegation is defined %} -id-assoc pd 2 { -{% if dhcpv6_options.prefix_delegation.length is defined %} - prefix ::/{{ dhcpv6_options.prefix_delegation.length }} infinity; -{% endif %} -{% for interface in dhcpv6_options.prefix_delegation.interface %} +{% if dhcpv6_options is defined and dhcpv6_options.pd is defined %} +{% for pd in dhcpv6_options.pd %} +id-assoc pd {{ pd }} { +{# length got a default value #} + prefix ::/{{ dhcpv6_options.pd[pd].length }} infinity; +{% set sla_len = 64 - dhcpv6_options.pd[pd].length|int %} +{% set count = namespace(value=0) %} +{% for interface in dhcpv6_options.pd[pd].interface if dhcpv6_options.pd[pd].interface is defined %} prefix-interface {{ interface }} { -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_id is defined %} - sla-id {{ dhcpv6_options.prefix_delegation.interface[interface].sla_id }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].sla_len is defined %} - sla-len {{ dhcpv6_options.prefix_delegation.interface[interface].sla_len }}; -{% endif %} -{% if dhcpv6_options.prefix_delegation.interface[interface].address is defined %} - ifid {{ dhcpv6_options.prefix_delegation.interface[interface].address }}; -{% endif %} + sla-len {{ sla_len }}; +{% if dhcpv6_options.pd[pd].interface[interface].sla_id is defined and dhcpv6_options.pd[pd].interface[interface].sla_id is not none %} + sla-id {{ dhcpv6_options.pd[pd].interface[interface].sla_id }}; +{% else %} + sla-id {{ count.value }}; +{% endif %} +{% if dhcpv6_options.pd[pd].interface[interface].address is defined and dhcpv6_options.pd[pd].interface[interface].address is not none %} + ifid {{ dhcpv6_options.pd[pd].interface[interface].address }}; +{% endif %} }; -{% endfor %} +{% set count.value = count.value + 1 %} +{% endfor %} }; +{% endfor %} {% endif %} diff --git a/interface-definitions/include/dhcp-options.xml.i b/interface-definitions/include/dhcp-options.xml.i index 0f71d9321..9989291fc 100644 --- a/interface-definitions/include/dhcp-options.xml.i +++ b/interface-definitions/include/dhcp-options.xml.i @@ -1,6 +1,6 @@ <node name="dhcp-options"> <properties> - <help>DHCP options</help> + <help>DHCP client settings/options</help> </properties> <children> <leafNode name="client-id"> diff --git a/interface-definitions/include/dhcpv6-options.xml.i b/interface-definitions/include/dhcpv6-options.xml.i index 98a87dba2..5d088b83d 100644 --- a/interface-definitions/include/dhcpv6-options.xml.i +++ b/interface-definitions/include/dhcpv6-options.xml.i @@ -1,11 +1,24 @@ <node name="dhcpv6-options"> <properties> - <help>DHCPv6 options</help> + <help>DHCPv6 client settings/options</help> </properties> <children> - <node name="prefix-delegation"> + <leafNode name="parameters-only"> + <properties> + <help>Acquire only config parameters, no address</help> + <valueless/> + </properties> + </leafNode> + <tagNode name="pd"> <properties> - <help>DHCPv6 Prefix Delegation Options</help> + <help>DHCPv6 prefix delegation interface statement</help> + <valueHelp> + <format>instance number</format> + <description>Prefix delegation instance (>= 0)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--non-negative"/> + </constraint> </properties> <children> <leafNode name="length"> @@ -19,6 +32,7 @@ <validator name="numeric" argument="--range 32-64"/> </constraint> </properties> + <defaultValue>64</defaultValue> </leafNode> <tagNode name="interface"> <properties> @@ -52,28 +66,10 @@ </constraint> </properties> </leafNode> - <leafNode name="sla-len"> - <properties> - <help>Site-Level aggregator (SLA) length</help> - <valueHelp> - <format>0-128</format> - <description>Length of delegated prefix</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-128"/> - </constraint> - </properties> - </leafNode> </children> </tagNode> </children> - </node> - <leafNode name="parameters-only"> - <properties> - <help>Acquire only config parameters, no address</help> - <valueless/> - </properties> - </leafNode> + </tagNode> <leafNode name="temporary"> <properties> <help>IPv6 "temporary" address</help> diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 010eda45c..c1e93955e 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -93,7 +93,7 @@ def dict_merge(source, destination): tmp = deepcopy(destination) for key, value in source.items(): - if key not in tmp.keys(): + if key not in tmp: tmp[key] = value elif isinstance(source[key], dict): tmp[key] = dict_merge(source[key], tmp[key]) @@ -109,15 +109,15 @@ def T2665_default_dict_cleanup(dict): """ Cleanup default keys for tag nodes https://phabricator.vyos.net/T2665. """ # Cleanup for vif in ['vif', 'vif_s']: - if vif in dict.keys(): - for key in ['ip', 'mtu']: - if key in dict[vif].keys(): + if vif in dict: + for key in ['ip', 'mtu', 'dhcpv6_options']: + if key in dict[vif]: del dict[vif][key] # cleanup VIF-S defaults - if 'vif_c' in dict[vif].keys(): - for key in ['ip', 'mtu']: - if key in dict[vif]['vif_c'].keys(): + if 'vif_c' in dict[vif]: + for key in ['ip', 'mtu', 'dhcpv6_options']: + if key in dict[vif]['vif_c']: del dict[vif]['vif_c'][key] # If there is no vif-c defined and we just cleaned the default # keys - we can clean the entire vif-c dict as it's useless @@ -129,6 +129,21 @@ def T2665_default_dict_cleanup(dict): if not dict[vif]: del dict[vif] + if 'dhcpv6_options' in dict and 'pd' in dict['dhcpv6_options']: + if 'length' in dict['dhcpv6_options']['pd']: + del dict['dhcpv6_options']['pd']['length'] + + # delete empty dicts + if 'dhcpv6_options' in dict: + if 'pd' in dict['dhcpv6_options']: + # test if 'pd' is an empty node so we can remove it + if not dict['dhcpv6_options']['pd']: + del dict['dhcpv6_options']['pd'] + + # test if 'dhcpv6_options' is an empty node so we can remove it + if not dict['dhcpv6_options']: + del dict['dhcpv6_options'] + return dict def leaf_node_changed(conf, path): @@ -193,6 +208,16 @@ def get_removed_vlans(conf, dict): return dict + +def dict_add_dhcpv6pd_defaults(defaults, config_dict): + # Implant default dictionary for DHCPv6-PD instances + if 'dhcpv6_options' in config_dict and 'pd' in config_dict['dhcpv6_options']: + for pd, pd_config in config_dict['dhcpv6_options']['pd'].items(): + config_dict['dhcpv6_options']['pd'][pd] = dict_merge( + defaults, pd_config) + + return config_dict + def get_interface_dict(config, base, ifname=''): """ Common utility function to retrieve and mandgle the interfaces available @@ -257,15 +282,30 @@ def get_interface_dict(config, base, ifname=''): # remove wrongly inserted values dict = T2665_default_dict_cleanup(dict) - # The values are identical for vif, vif-s and vif-c as the all include the same - # XML definitions which hold the defaults + # Implant default dictionary for DHCPv6-PD instances + default_pd_values = defaults(base + ['dhcpv6-options', 'pd']) + dict = dict_add_dhcpv6pd_defaults(default_pd_values, dict) + + # Implant default dictionary in vif/vif-s VLAN interfaces. Values are + # identical for all types of VLAN interfaces as they all include the same + # XML definitions which hold the defaults. default_vif_values = defaults(base + ['vif']) for vif, vif_config in dict.get('vif', {}).items(): - vif_config = dict_merge(default_vif_values, vif_config) + dict['vif'][vif] = dict_add_dhcpv6pd_defaults( + default_pd_values, vif_config) + dict['vif'][vif] = T2665_default_dict_cleanup( + dict_merge(default_vif_values, vif_config)) + for vif_s, vif_s_config in dict.get('vif_s', {}).items(): - vif_s_config = dict_merge(default_vif_values, vif_s_config) + dict['vif_s'][vif_s] = dict_add_dhcpv6pd_defaults( + default_pd_values, vif_s_config) + dict['vif_s'][vif_s] = T2665_default_dict_cleanup( + dict_merge(default_vif_values, vif_s_config)) for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items(): - vif_c_config = dict_merge(default_vif_values, vif_c_config) + dict['vif_s'][vif_s]['vif_c'][vif_c] = dict_add_dhcpv6pd_defaults( + default_pd_values, vif_c_config) + dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_default_dict_cleanup( + dict_merge(default_vif_values, vif_c_config)) # Check vif, vif-s/vif-c VLAN interfaces for removal dict = get_removed_vlans(config, dict) diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index 928113b49..901ea769c 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -15,7 +15,6 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os -import jmespath from sys import exit from copy import deepcopy @@ -104,8 +103,7 @@ def generate(pppoe): render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', pppoe, trim_blocks=True, permission=0o755) - tmp = jmespath.search('dhcpv6_options.prefix_delegation.interface', pppoe) - if tmp and len(tmp) > 0: + if 'dhcpv6_options' in pppoe and 'pd' in pppoe['dhcpv6_options']: # ipv6.tmpl relies on ifname - this should be made consitent in the # future better then double key-ing the same value render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe, trim_blocks=True) diff --git a/src/migration-scripts/interfaces/11-to-12 b/src/migration-scripts/interfaces/11-to-12 new file mode 100755 index 000000000..0dad24642 --- /dev/null +++ b/src/migration-scripts/interfaces/11-to-12 @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# - rename 'dhcpv6-options prefix-delegation' from single node to a new tag node +# 'dhcpv6-options pd 0' +# - delete 'sla-len' from CLI - value is calculated on demand + +from sys import exit, argv +from vyos.configtree import ConfigTree + +if __name__ == '__main__': + if (len(argv) < 1): + print("Must specify file name!") + exit(1) + + file_name = argv[1] + with open(file_name, 'r') as f: + config_file = f.read() + + config = ConfigTree(config_file) + + for type in config.list_nodes(['interfaces']): + for interface in config.list_nodes(['interfaces', type]): + # cache current config tree + base_path = ['interfaces', type, interface, 'dhcpv6-options'] + old_base = base_path + ['prefix-delegation'] + new_base = base_path + ['pd'] + if config.exists(old_base): + config.set(new_base) + config.set_tag(new_base) + config.copy(old_base, new_base + ['0']) + config.delete(old_base) + + for pd in config.list_nodes(new_base): + for tmp in config.list_nodes(new_base + [pd, 'interface']): + sla_config = new_base + [pd, 'interface', tmp, 'sla-len'] + if config.exists(sla_config): + config.delete(sla_config) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + exit(1) |