diff options
-rw-r--r-- | data/configd-include.json | 1 | ||||
-rw-r--r-- | data/templates/frr/isis.frr.tmpl | 121 | ||||
-rw-r--r-- | debian/control | 1 | ||||
-rw-r--r-- | interface-definitions/include/isis-redistribute-ipv4.xml.i | 36 | ||||
-rw-r--r-- | interface-definitions/system-options.xml.in | 33 | ||||
-rw-r--r-- | python/vyos/configverify.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-openvpn.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/protocols_isis.py | 152 | ||||
-rwxr-xr-x | src/conf_mode/system-options.py | 30 | ||||
-rwxr-xr-x | src/op_mode/powerctrl.py | 6 |
10 files changed, 343 insertions, 41 deletions
diff --git a/data/configd-include.json b/data/configd-include.json index da6fb915f..2e44405ee 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -30,6 +30,7 @@ "nat.py", "ntp.py", "protocols_igmp.py", +"protocols_isis.py", "protocols_mpls.py", "protocols_pim.py", "protocols_rip.py", diff --git a/data/templates/frr/isis.frr.tmpl b/data/templates/frr/isis.frr.tmpl new file mode 100644 index 000000000..929f5bdb2 --- /dev/null +++ b/data/templates/frr/isis.frr.tmpl @@ -0,0 +1,121 @@ +! +router isis {{ process }} + net {{ net }} +{% if dynamic_hostname is defined %} + hostname dynamic +{% endif %} +{% if purge_originator is defined %} + purge-originator +{% endif %} +{% if set_attached_bit is defined %} + set-attached-bit +{% endif %} +{% if set_overload_bit is defined %} + set-overload-bit +{% endif %} +{% if domain_password is defined and domain_password.plaintext_password is defined and domain_password.plaintext_password is not none %} + domain-password clear {{ domain_password.plaintext_password }} +{% endif %} +{% if lsp_gen_interval is defined and lsp_gen_interval is not none %} + lsp-gen-interval {{ lsp_gen_interval }} +{% endif %} +{% if lsp_mtu is defined and lsp_mtu is not none %} + lsp-mtu {{ lsp_mtu }} +{% endif %} +{% if lsp_refresh_interval is defined and lsp_refresh_interval is not none %} + lsp-refresh-interval {{ lsp_refresh_interval }} +{% endif %} +{% if max_lsp_lifetime is defined and max_lsp_lifetime is not none %} + max-lsp-lifetime {{ max_lsp_lifetime }} +{% endif %} +{% if spf_interval is defined and spf_interval is not none %} + spf-interval {{ spf_interval }} +{% endif %} +{% if spf_delay_ietf is defined and spf_delay_ietf.init_delay is defined and spf_delay_ietf.init_delay is not none %} + spf-delay-ietf init-delay {{ spf_delay_ietf.init_delay }} +{% endif %} +{% if area_password is defined and area_password.md5 is defined and area_password.md5 is not none %} + area-password md5 {{ area_password.md5 }} +{% elif area_password is defined and area_password.plaintext_password is defined and area_password.plaintext_password is not none %} + area-password clear {{ area_password.plaintext_password }} +{% endif %} +{% if default_information is defined and default_information.originate is defined and default_information.originate is not none %} +{% for level in default_information.originate.ipv4 if default_information.originate.ipv4 is defined %} + default-information originate ipv4 {{ level | replace('_', '-') }} +{% endfor %} +{% for level in default_information.originate.ipv6 if default_information.originate.ipv6 is defined %} + default-information originate ipv6 {{ level | replace('_', '-') }} always +{% endfor %} +{% endif %} +{% if redistribute is defined and redistribute.ipv4 is defined and redistribute.ipv4 is not none %} +{% for protocol in redistribute.ipv4 %} +{% for level, level_config in redistribute.ipv4[protocol].items() %} +{% if level_config.metric is defined and level_config.metric is not none %} + redistribute ipv4 {{ protocol }} {{ level | replace('_', '-') }} metric {{ level_config.metric }} +{% elif level_config.route_map is defined and level_config.route_map is not none %} + redistribute ipv4 {{ protocol }} {{ level | replace('_', '-') }} route-map {{ level_config.route_map }} +{% else %} + redistribute ipv4 {{ protocol }} {{ level | replace('_', '-') }} +{% endif %} +{% endfor %} +{% endfor %} +{% endif %} +{% if level is defined and level is not none %} +{% if level == 'level-1' %} + is-type level-1 +{% elif level == 'level-2' %} + is-type level-2-only +{% elif level == 'level-1-2' %} + is-type level-1-2 +{% endif %} +{% endif %} +! +{% if interface_remove is defined and interface_remove is not none %} +{% for iface in interface_remove %} +interface {{ iface }} + no ip router isis +{% endfor %} +{% endif %} +! +{% if interface is defined and interface is not none %} +{% for iface, iface_config in interface.items() %} +interface {{ iface }} + ip router isis {{ process }} +{% if iface_config.bfd is defined %} + isis bfd +{% endif %} +{% if iface_config.network is defined and iface_config.network.point_to_point is defined %} + isis network point-to-point +{% endif %} +{% if iface_config.circuit_type is defined %} + isis circuit-type {{ iface_config.circuit_type }} +{% endif %} +{% if iface_config.hello_interval is defined and iface_config.hello_interval is not none %} + isis hello-interval {{ iface_config.hello_interval }} +{% endif %} +{% if iface_config.hello_multiplier is defined and iface_config.hello_multiplier is not none %} + isis hello-multiplier {{ iface_config.hello_multiplier }} +{% endif %} +{% if iface_config.hello_padding is defined %} + isis hello padding +{% endif %} +{% if iface_config.metric is defined and iface_config.metric is not none %} + isis metric {{ iface_config.metric }} +{% endif %} +{% if iface_config.passive is defined %} + isis passive +{% endif %} +{% if iface_config.password is defined and iface_config.password.plaintext_password is defined and iface_config.password.plaintext_password is not none %} + isis password clear {{ iface_config.password.plaintext_password }} +{% endif %} +{% if iface_config.priority is defined and iface_config.priority is not none %} + isis priority {{ iface_config.priority }} +{% endif %} +{% if iface_config.psnp_interval is defined and iface_config.psnp_interval is not none %} + isis psnp-interval {{ iface_config.psnp_interval }} +{% endif %} +{% if iface_config.three_way_handshake is defined %} + isis three-way-handshake +{% endif %} +{% endfor %} +{% endif %} diff --git a/debian/control b/debian/control index c5dda7883..a290952a3 100644 --- a/debian/control +++ b/debian/control @@ -28,6 +28,7 @@ Depends: conntrack, conserver-client, conserver-server, + console-data, crda, cron, dbus, diff --git a/interface-definitions/include/isis-redistribute-ipv4.xml.i b/interface-definitions/include/isis-redistribute-ipv4.xml.i index a40d8dfc2..fd5e75918 100644 --- a/interface-definitions/include/isis-redistribute-ipv4.xml.i +++ b/interface-definitions/include/isis-redistribute-ipv4.xml.i @@ -16,28 +16,14 @@ </constraint> </properties> </leafNode> - <tagNode name="route-map"> + <leafNode name="route-map"> <properties> <help>Route map reference</help> <completionHelp> <path>policy route-map</path> </completionHelp> </properties> - <children> - <leafNode name="metric"> - <properties> - <help>Metric for redistributed routes</help> - <valueHelp> - <format><0-16777215></format> - <description>ISIS default metric</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-16777215"/> - </constraint> - </properties> - </leafNode> - </children> - </tagNode> + </leafNode> </children> </node> <node name="level-2"> @@ -57,28 +43,14 @@ </constraint> </properties> </leafNode> - <tagNode name="route-map"> + <leafNode name="route-map"> <properties> <help>Route map reference</help> <completionHelp> <path>policy route-map</path> </completionHelp> </properties> - <children> - <leafNode name="metric"> - <properties> - <help>Metric for redistributed routes</help> - <valueHelp> - <format><0-16777215></format> - <description>ISIS default metric</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 0-16777215"/> - </constraint> - </properties> - </leafNode> - </children> - </tagNode> + </leafNode> </children> </node> <!-- included end --> diff --git a/interface-definitions/system-options.xml.in b/interface-definitions/system-options.xml.in index 297f5891e..12e4044af 100644 --- a/interface-definitions/system-options.xml.in +++ b/interface-definitions/system-options.xml.in @@ -80,6 +80,39 @@ #include <include/source-address-ipv4-ipv6.xml.i> </children> </node> + <leafNode name="keyboard-layout"> + <properties> + <help>System keyboard layout, type ISO2</help> + <completionHelp> + <list>us fr de fi no dk</list> + </completionHelp> + <valueHelp> + <format>us</format> + <description>United States of America</description> + </valueHelp> + <valueHelp> + <format>fr</format> + <description>France</description> + </valueHelp> + <valueHelp> + <format>de</format> + <description>Germany</description> + </valueHelp> + <valueHelp> + <format>fi</format> + <description>Finland</description> + </valueHelp> + <valueHelp> + <format>no</format> + <description>Norway</description> + </valueHelp> + <valueHelp> + <format>dk</format> + <description>Denmark</description> + </valueHelp> + </properties> + <defaultValue>us</defaultValue> + </leafNode> </children> </node> </children> diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 2a5dc7af2..675dac5b1 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -278,7 +278,7 @@ def verify_diffie_hellman_length(file, min_keysize): prog = re.compile('\d+\s+bit') if prog.search(out): bits = prog.search(out)[0].split()[0] - if int(min_keysize) >= int(bits): + if int(bits) >= int(min_keysize): return True return False diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 7d5f7f3a0..c23e79948 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -320,7 +320,7 @@ def verify(openvpn): if 'local_port' in openvpn: raise ConfigError('Cannot specify "local-port" with "tcp-active"') - if 'remote_host' in openvpn: + if 'remote_host' not in openvpn: raise ConfigError('Must specify "remote-host" with "tcp-active"') # shared secret and TLS diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py new file mode 100755 index 000000000..d5e5b64fb --- /dev/null +++ b/src/conf_mode/protocols_isis.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2017-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/>. + +import os + +from sys import exit + +from vyos.config import Config +from vyos.configdict import node_changed +from vyos import ConfigError +from vyos.util import call +from vyos.template import render +from vyos.template import render_to_string +from vyos import frr +from vyos import airbag +airbag.enable() + +config_file = r'/tmp/isis.frr' + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + base = ['protocols', 'isis'] + + isis = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # determine which members have been removed + for instance in isis: + conf.set_level(base + [instance]) + tmp = node_changed(conf, ['interface']) + if tmp: + isis[instance].update({'interface_remove': tmp}) + + return isis + +def verify(isis): + # bail out early - looks like removal from running config + if not isis: + return None + + for process, isis_config in isis.items(): + # If more then one isis process is defined (Frr only supports one) + # http://docs.frrouting.org/en/latest/isisd.html#isis-router + if len(isis) > 1: + raise ConfigError('Only one isis process can be definded') + + # If network entity title (net) not defined + if 'net' not in isis_config: + raise ConfigError('ISIS net format iso is mandatory!') + + # If interface not set + if 'interface' not in isis_config: + raise ConfigError('ISIS interface is mandatory!') + + # If md5 and plaintext-password set at the same time + if 'area_password' in isis_config: + if {'md5', 'plaintext_password'} <= set(isis_config['encryption']): + raise ConfigError('Can not use both md5 and plaintext-password for ISIS area-password!') + + # If one param from deley set, but not set others + if 'spf_delay_ietf' in isis_config: + required_timers = ['holddown', 'init_delay', 'long_delay', 'short_delay', 'time_to_learn'] + exist_timers = [] + for elm_timer in required_timers: + if elm_timer in isis_config['spf_delay_ietf']: + exist_timers.append(elm_timer) + + exist_timers = set(required_timers).difference(set(exist_timers)) + if len(exist_timers) > 0: + raise ConfigError('All types of delay must be specified: ' + ', '.join(exist_timers).replace('_', '-')) + + # If Redistribute set, but level don't set + if 'redistribute' in isis_config: + proc_level = isis_config.get('level','').replace('-','_') + for proto, proto_config in isis_config.get('redistribute', {}).get('ipv4', {}).items(): + if 'level_1' not in proto_config and 'level_2' not in proto_config: + raise ConfigError('Redistribute level-1 or level-2 should be specified in \"protocols isis {} redistribute ipv4 {}\"'.format(process, proto)) + for redistribute_level in proto_config.keys(): + if proc_level and proc_level != 'level_1_2' and proc_level != redistribute_level: + raise ConfigError('\"protocols isis {0} redistribute ipv4 {2} {3}\" cannot be used with \"protocols isis {0} level {1}\"'.format(process, proc_level, proto, redistribute_level)) + + return None + +def generate(isis): + if not isis: + isis['new_frr_config'] = '' + return None + + # only one ISIS process is supported, so we can directly send the first key + # of the config dict + process = list(isis.keys())[0] + isis[process]['process'] = process + + import pprint + pprint.pprint(isis[process]) + + # render(config) not needed, its only for debug + render(config_file, 'frr/isis.frr.tmpl', isis[process], trim_blocks=True) + + isis['new_frr_config'] = render_to_string('frr/isis.frr.tmpl', + isis[process], trim_blocks=True) + + return None + +def apply(isis): + + # Save original configration prior to starting any commit actions + frr_cfg = {} + frr_cfg['original_config'] = frr.get_configuration(daemon='isisd') + frr_cfg['modified_config'] = frr.replace_section(frr_cfg['original_config'], isis['new_frr_config'], from_re='router isis .*') + + # Debugging + print('') + print('--------- DEBUGGING ----------') + print(f'Existing config:\n{frr_cfg["original_config"]}\n\n') + print(f'Replacement config:\n{isis["new_frr_config"]}\n\n') + print(f'Modified config:\n{frr_cfg["modified_config"]}\n\n') + + # FRR mark configuration will test for syntax errors and throws an + # exception if any syntax errors is detected + frr.mark_configuration(frr_cfg['modified_config']) + + # Commit resulting configuration to FRR, this will throw CommitError + # on failure + frr.reload_configuration(frr_cfg['modified_config'], daemon='isisd') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/conf_mode/system-options.py b/src/conf_mode/system-options.py index 22765cef7..1061b90ac 100755 --- a/src/conf_mode/system-options.py +++ b/src/conf_mode/system-options.py @@ -18,11 +18,14 @@ import os from netifaces import interfaces from sys import exit +from time import sleep from vyos.config import Config +from vyos.configdict import dict_merge from vyos.template import render from vyos.util import cmd from vyos.validate import is_addr_assigned +from vyos.xml import defaults from vyos import ConfigError from vyos import airbag airbag.enable() @@ -38,6 +41,12 @@ def get_config(config=None): conf = Config() base = ['system', 'options'] options = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = defaults(base) + options = dict_merge(default_values, options) + return options def verify(options): @@ -69,8 +78,8 @@ def generate(options): return None def apply(options): - # Beep action - if 'beep_if_fully_booted' in options.keys(): + # System bootup beep + if 'beep_if_fully_booted' in options: cmd('systemctl enable vyos-beep.service') else: cmd('systemctl disable vyos-beep.service') @@ -78,34 +87,43 @@ def apply(options): # Ctrl-Alt-Delete action if os.path.exists(systemd_action_file): os.unlink(systemd_action_file) - if 'ctrl_alt_del_action' in options: if options['ctrl_alt_del_action'] == 'reboot': os.symlink('/lib/systemd/system/reboot.target', systemd_action_file) elif options['ctrl_alt_del_action'] == 'poweroff': os.symlink('/lib/systemd/system/poweroff.target', systemd_action_file) + # Configure HTTP client if 'http_client' not in options: if os.path.exists(curlrc_config): os.unlink(curlrc_config) + # Configure SSH client if 'ssh_client' not in options: if os.path.exists(ssh_config): os.unlink(ssh_config) # Reboot system on kernel panic with open('/proc/sys/kernel/panic', 'w') as f: - if 'reboot_on_panic' in options.keys(): + if 'reboot_on_panic' in options: f.write('60') else: f.write('0') # tuned - performance tuning if 'performance' in options: - cmd('systemctl enable tuned.service') + cmd('systemctl restart tuned.service') + # wait until daemon has started before sending configuration + while (int(os.system('systemctl is-active --quiet tuned.service')) != 0): + sleep(0.250) cmd('tuned-adm profile network-{performance}'.format(**options)) else: - cmd('systemctl disable tuned.service') + cmd('systemctl stop tuned.service') + + # Keyboard layout - there will be always the default key inside the dict + # but we check for key existence anyway + if 'keyboard_layout' in options: + cmd('loadkeys -C /dev/console {keyboard_layout}'.format(**options)) if __name__ == '__main__': try: diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py index 69af427ec..c000d7d06 100755 --- a/src/op_mode/powerctrl.py +++ b/src/op_mode/powerctrl.py @@ -34,7 +34,11 @@ def utc2local(datetime): def parse_time(s): try: if re.match(r'^\d{1,2}$', s): - return datetime.strptime(s, "%M").time() + if (int(s) > 59): + s = str(int(s)//60) + ":" + str(int(s)%60) + return datetime.strptime(s, "%H:%M").time() + else: + return datetime.strptime(s, "%M").time() else: return datetime.strptime(s, "%H:%M").time() except ValueError: |