summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--debian/control1
-rw-r--r--interface-definitions/service-router-advert.xml.in266
-rwxr-xr-xsrc/conf_mode/service-router-advert.py207
-rwxr-xr-xsrc/migration-scripts/interfaces/5-to-6111
4 files changed, 585 insertions, 0 deletions
diff --git a/debian/control b/debian/control
index 53c4130d7..366e8df94 100644
--- a/debian/control
+++ b/debian/control
@@ -77,6 +77,7 @@ Depends: python3,
iperf,
iperf3,
frr,
+ radvd,
dbus,
hostapd (>= 0.6.8),
wpasupplicant (>= 0.6.7),
diff --git a/interface-definitions/service-router-advert.xml.in b/interface-definitions/service-router-advert.xml.in
new file mode 100644
index 000000000..bd63b15a3
--- /dev/null
+++ b/interface-definitions/service-router-advert.xml.in
@@ -0,0 +1,266 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="router-advert" owner="${vyos_conf_scripts_dir}/service-router-advert.py">
+ <properties>
+ <help>IPv6 Router Advertisements (RAs) service</help>
+ <priority>900</priority>
+ </properties>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Interface to send DDNS updates for [REQUIRED]</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="hop-limit">
+ <properties>
+ <help>Set Hop Count field of the IP header for outgoing packets (default: 64)</help>
+ <valueHelp>
+ <format>1-255</format>
+ <description>Value should represent current diameter of the Internet</description>
+ </valueHelp>
+ <valueHelp>
+ <format>0</format>
+ <description>Unspecified (by this router)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ <constraintErrorMessage>Hop count must be between 0 and 255</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="default-lifetime">
+ <properties>
+ <help>Lifetime associated with the default router in units of seconds</help>
+ <valueHelp>
+ <format>4-9000</format>
+ <description>Router Lifetime in seconds</description>
+ </valueHelp>
+ <valueHelp>
+ <format>0</format>
+ <description>Not a default router</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-0 --range 4-9000"/>
+ </constraint>
+ <constraintErrorMessage>Default router livetime bust be 0 or between 4 and 9000</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="default-preference">
+ <properties>
+ <help>Preference associated with the default router,</help>
+ <completionHelp>
+ <list>low medium high</list>
+ </completionHelp>
+ <valueHelp>
+ <format>low</format>
+ <description>Default router has low preference</description>
+ </valueHelp>
+ <valueHelp>
+ <format>medium</format>
+ <description>Default router has medium preference (default)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>high</format>
+ <description>Default router has high preference</description>
+ </valueHelp>
+ <constraint>
+ <regex>(low|medium|high)</regex>
+ </constraint>
+ <constraintErrorMessage>Default preference must be low, medium or high</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="dnssl">
+ <properties>
+ <help>DNS search list</help>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="link-mtu">
+ <properties>
+ <help>Link MTU value placed in RAs, exluded in RAs if unset</help>
+ <valueHelp>
+ <format>1280-9000</format>
+ <description>Link MTU value in RAs</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1280-9000"/>
+ </constraint>
+ <constraintErrorMessage>Link MTU must be between 1280 and 9000</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="managed-flag">
+ <properties>
+ <help>Hosts use the administered (stateful) protocol for address autoconfiguration in addition to any addresses autoconfigured using SLAAC</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="interval">
+ <properties>
+ <help>Set interval between unsolicited multicast RAs</help>
+ </properties>
+ <children>
+ <leafNode name="max">
+ <properties>
+ <help>Maximum interval between unsolicited multicast RAs (default: 600)</help>
+ <valueHelp>
+ <format>4-1800</format>
+ <description>Maximum interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 4-1800"/>
+ </constraint>
+ <constraintErrorMessage>Maximum interval must be between 4 and 1800 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="min">
+ <properties>
+ <help>Minimum interval between unsolicited multicast RAs</help>
+ <valueHelp>
+ <format>3-1350</format>
+ <description>Minimum interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 3-1350"/>
+ </constraint>
+ <constraintErrorMessage>Minimum interval must be between 3 and 1350 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="name-server">
+ <properties>
+ <help>IPv6 address of recursive DNS server</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address of DNS name server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="other-config-flag">
+ <properties>
+ <help>Hosts use the administered (stateful) protocol for autoconfiguration of other (non-address) information</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <tagNode name="prefix">
+ <properties>
+ <help>IPv6 prefix to be advertised in Router Advertisements (RAs)</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 prefix to be advertized</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="no-autonomous-flag">
+ <properties>
+ <help>Prefix can not be used for stateless address auto-configuration</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="no-on-link-flag">
+ <properties>
+ <help>Prefix can not be used for on-link determination</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="preferred-lifetime">
+ <properties>
+ <help>Time in seconds that the prefix will remain preferred (default 4 hours)</help>
+ <completionHelp>
+ <list>infinity</list>
+ </completionHelp>
+ <valueHelp>
+ <format>0-4294967295</format>
+ <description>Time in seconds that the prefix will remain preferred</description>
+ </valueHelp>
+ <valueHelp>
+ <format>infinity</format>
+ <description>Prefix will remain preferred forever</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ <regex>(infinity)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="valid-lifetime">
+ <properties>
+ <help>Time in seconds that the prefix will remain valid (default: 30 days)</help>
+ <completionHelp>
+ <list>infinity</list>
+ </completionHelp>
+ <valueHelp>
+ <format>1-4294967295</format>
+ <description>Time in seconds that the prefix will remain valid</description>
+ </valueHelp>
+ <valueHelp>
+ <format>infinity</format>
+ <description>Prefix will remain preferred forever</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ <regex>(infinity)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="reachable-time">
+ <properties>
+ <help>Time, in milliseconds, that a node assumes a neighbor is reachable after having received a reachability confirmation</help>
+ <valueHelp>
+ <format>1-3600000</format>
+ <description>Reachable Time value in RAs (in milliseconds)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>0</format>
+ <description>Reachable Time unspecified by this router</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-0 --range 1-3600000"/>
+ </constraint>
+ <constraintErrorMessage>Reachable time must be 0 or between 1 and 3600000 milliseconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="retrans-timer">
+ <properties>
+ <help>Time in milliseconds between retransmitted Neighbor Solicitation messages</help>
+ <valueHelp>
+ <format>1-4294967295</format>
+ <description>Minimum interval in milliseconds</description>
+ </valueHelp>
+ <valueHelp>
+ <format>0</format>
+ <description>Time, in milliseconds, between retransmitted Neighbor Solicitation messages</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-0 --range 1-4294967295"/>
+ </constraint>
+ <constraintErrorMessage>Retransmit interval must be 0 or between 1 and 4294967295 milliseconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="no-send-advert">
+ <properties>
+ <help>Do not send router adverts</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/src/conf_mode/service-router-advert.py b/src/conf_mode/service-router-advert.py
new file mode 100755
index 000000000..87a4e9d92
--- /dev/null
+++ b/src/conf_mode/service-router-advert.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018-2019 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/>.
+
+import os
+import sys
+import jinja2
+
+from stat import S_IRUSR, S_IWUSR, S_IRGRP
+from vyos.config import Config
+from vyos import ConfigError
+
+config_file = r'/etc/radvd.conf'
+
+config_tmpl = """
+### Autogenerated by service-ipv6-ra.py ###
+
+{% for i in interfaces -%}
+interface {{ i.name }} {
+ IgnoreIfMissing on;
+ AdvDefaultPreference {{ i.default_preference }};
+ AdvManagedFlag {{ i.managed_flag }};
+ MaxRtrAdvInterval {{ i.interval_max }};
+{% if i.interval_min %}
+ MinRtrAdvInterval {{ i.interval_min }};
+{% endif %}
+ AdvReachableTime {{ i.reachable_time }};
+ AdvIntervalOpt {{ i.send_advert }};
+ AdvSendAdvert {{ i.send_advert }};
+{% if i.default_lifetime %}
+ AdvDefaultLifetime {{ i.default_lifetime }};
+{% endif %}
+ AdvLinkMTU {{ i.link_mtu }};
+ AdvOtherConfigFlag {{ i.other_config_flag }};
+ AdvRetransTimer {{ i.retrans_timer }};
+ AdvCurHopLimit {{ i.hop_limit }};
+{% for p in i.prefixes %}
+ prefix {{ p.prefix }} {
+ AdvAutonomous {{ p.autonomous_flag }};
+ AdvValidLifetime {{ p.valid_lifetime }};
+ AdvOnLink {{ p.on_link }};
+ AdvPreferredLifetime {{ p.preferred_lifetime }};
+ };
+{% endfor %}
+{% if i.name_server %}
+ RDNSS {{ i.name_server | join(" ") }} {
+ };
+{% endif %}
+};
+{% endfor -%}
+"""
+
+default_config_data = {
+ 'interfaces': []
+}
+
+def get_config():
+ rtradv = default_config_data
+ conf = Config()
+ base_level = ['service', 'router-advert']
+
+ if not conf.exists(base_level):
+ return rtradv
+
+ for interface in conf.list_nodes(base_level + ['interface']):
+ intf = {
+ 'name': interface,
+ 'hop_limit' : '64',
+ 'default_lifetime': '',
+ 'default_preference': 'medium',
+ 'dnssl': [],
+ 'link_mtu': '0',
+ 'managed_flag': 'off',
+ 'interval_max': '600',
+ 'interval_min': '',
+ 'name_server': [],
+ 'other_config_flag': 'off',
+ 'prefixes' : [],
+ 'reachable_time': '0',
+ 'retrans_timer': '0',
+ 'send_advert': 'on'
+ }
+
+ # set config level first to reduce boilerplate code
+ conf.set_level(base_level + ['interface', interface])
+
+ if conf.exists(['hop-limit']):
+ intf['hop_limit'] = conf.return_value(['hop-limit'])
+
+ if conf.exists(['default-lifetim']):
+ intf['default_lifetime'] = conf.return_value(['default-lifetim'])
+
+ if conf.exists(['default-preference']):
+ intf['default_preference'] = conf.return_value(['default-preference'])
+
+ if conf.exists(['dnssl']):
+ intf['dnssl'] = conf.return_values(['dnssl'])
+
+ if conf.exists(['link-mtu']):
+ intf['link_mtu'] = conf.return_value(['link-mtu'])
+
+ if conf.exists(['managed-flag']):
+ intf['managed_flag'] = 'on'
+
+ if conf.exists(['interval', 'max']):
+ intf['interval_max'] = conf.return_value(['interval', 'max'])
+
+ if conf.exists(['interval', 'min']):
+ intf['interval_min'] = conf.return_value(['interval', 'min'])
+
+ if conf.exists(['name-server']):
+ intf['name_server'] = conf.return_values(['name-server'])
+
+ if conf.exists(['other-config-flag']):
+ intf['other_config_flag'] = 'on'
+
+ if conf.exists(['reachable-time']):
+ intf['reachable_time'] = conf.return_value(['reachable-time'])
+
+ if conf.exists(['retrans-timer']):
+ intf['retrans_timer'] = conf.return_value(['retrans-timer'])
+
+ if conf.exists(['no-send-advert']):
+ intf['send_advert'] = 'off'
+
+ for prefix in conf.list_nodes(['prefix']):
+ tmp = {
+ 'prefix' : prefix,
+ 'autonomous_flag' : 'on',
+ 'on_link' : 'on',
+ 'preferred_lifetime': '14400',
+ 'valid_lifetime' : '2592000'
+
+ }
+
+ # set config level first to reduce boilerplate code
+ conf.set_level(base_level + ['interface', interface, 'prefix', prefix])
+
+ if conf.exists(['no-autonomous-flag']):
+ tmp['autonomous_flag'] = 'off'
+
+ if conf.exists(['no-on-link-flag']):
+ tmp['on_link'] = 'off'
+
+ if conf.exists(['preferred-lifetime']):
+ tmp['preferred_lifetime'] = conf.return_value(['preferred-lifetime'])
+
+ if conf.exists(['valid-lifetime']):
+ tmp['valid_lifetime'] = conf.return_value(['valid-lifetime'])
+
+ intf['prefixes'].append(tmp)
+
+ rtradv['interfaces'].append(intf)
+
+ return rtradv
+
+def verify(rtradv):
+ return None
+
+def generate(rtradv):
+ if not rtradv['interfaces']:
+ return None
+
+ tmpl = jinja2.Template(config_tmpl, trim_blocks=True)
+ config_text = tmpl.render(rtradv)
+ with open(config_file, 'w') as f:
+ f.write(config_text)
+
+ # adjust file permissions of new configuration file
+ if os.path.exists(config_file):
+ os.chmod(config_file, S_IRUSR | S_IWUSR | S_IRGRP)
+
+ return None
+
+def apply(rtradv):
+ if not rtradv['interfaces']:
+ # bail out early - looks like removal from running config
+ os.system('sudo systemctl stop radvd.service')
+ if os.path.exists(config_file):
+ os.unlink(config_file)
+
+ return None
+
+ os.system('sudo systemctl restart radvd.service')
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/5-to-6 b/src/migration-scripts/interfaces/5-to-6
new file mode 100755
index 000000000..9dbfd30e1
--- /dev/null
+++ b/src/migration-scripts/interfaces/5-to-6
@@ -0,0 +1,111 @@
+#!/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/>.
+
+# Migrate IPv6 router advertisments from a nested interface configuration to
+# a denested "service router-advert"
+
+import sys
+from vyos.configtree import ConfigTree
+
+def copy_rtradv(c, old_base, interface):
+ base = ['service', 'router-advert', 'interface']
+
+ if c.exists(old_base):
+ if not c.exists(base):
+ c.set(base)
+ c.set_tag(base)
+
+ # take the old node as a whole and copy it to new new path,
+ # additional migrations will be done afterwards
+ new_base = base + [interface]
+ c.copy(old_base, new_base)
+ c.delete(old_base)
+
+ # cur-hop-limit has been renamed to hop-limit
+ if c.exists(new_base + ['cur-hop-limit']):
+ c.rename(new_base + ['cur-hop-limit'], 'hop-limit')
+
+ bool_cleanup = ['managed-flag', 'other-config-flag']
+ for bool in bool_cleanup:
+ if c.exists(new_base + [bool]):
+ tmp = c.return_value(new_base + [bool])
+ c.delete(new_base + [bool])
+ if tmp == 'true':
+ c.set(new_base + [bool])
+
+ # max/min interval moved to subnode
+ intervals = ['max-interval', 'min-interval']
+ for interval in intervals:
+ if c.exists(new_base + [interval]):
+ tmp = c.return_value(new_base + [interval])
+ c.delete(new_base + [interval])
+ min_max = interval.split('-')[0]
+ c.set(new_base + ['interval', min_max], value=tmp)
+
+ # cleanup boolean nodes in individual prefix
+ prefix_base = new_base + ['prefix']
+ if c.exists(prefix_base):
+ for prefix in config.list_nodes(prefix_base):
+ bool_cleanup = ['autonomous-flag', 'on-link-flag']
+ for bool in bool_cleanup:
+ if c.exists(prefix_base + [prefix, bool]):
+ tmp = c.return_value(prefix_base + [prefix, bool])
+ c.delete(prefix_base + [prefix, bool])
+ if tmp == 'true':
+ c.set(prefix_base + [prefix, bool])
+
+ # router advertisement can be individually disabled per interface
+ # the node has been renamed from send-advert {true | false} to no-send-advert
+ if c.exists(new_base + ['send-advert']):
+ tmp = c.return_value(new_base + ['send-advert'])
+ c.delete(new_base + ['send-advert'])
+ if tmp == 'false':
+ c.set(new_base + ['no-send-advert'])
+
+if __name__ == '__main__':
+ if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = sys.argv[1]
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+
+ # list all individual interface types like dummy, ethernet and so on
+ for if_type in config.list_nodes(['interfaces']):
+ base_if_type = ['interfaces', if_type]
+
+ # for every individual interface we need to check if there is an
+ # ipv6 ra configured ... and also for every VIF (VLAN) interface
+ for intf in config.list_nodes(base_if_type):
+ old_base = base_if_type + [intf, 'ipv6', 'router-advert']
+ copy_rtradv(config, old_base, intf)
+
+ vif_base = base_if_type + [intf, 'vif']
+ if config.exists(vif_base):
+ for vif in config.list_nodes(vif_base):
+ old_base = vif_base + [vif, 'ipv6', 'router-advert']
+ vlan_name = f'{intf}.{vif}'
+ copy_rtradv(config, old_base, vlan_name)
+
+ 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))
+ sys.exit(1)