summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2020-08-22 23:15:29 +0200
committerChristian Poessinger <christian@poessinger.com>2020-08-22 23:29:11 +0200
commit98c2c5e0585e0806099a353de207f392223eff9a (patch)
tree1196c011cb24b099cc73d87d13aa5f9c97f33472
parent50a7544ad69cb7b631a499299f9ab32b2e8918a0 (diff)
downloadvyos-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.tmpl48
-rw-r--r--interface-definitions/include/dhcp-options.xml.i2
-rw-r--r--interface-definitions/include/dhcpv6-options.xml.i40
-rw-r--r--python/vyos/configdict.py64
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py4
-rwxr-xr-xsrc/migration-scripts/interfaces/11-to-1258
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)