From e41ae4d952e276d8497d38f5761806c14ea542d2 Mon Sep 17 00:00:00 2001 From: DmitriyEshenko Date: Wed, 9 Sep 2020 06:45:40 +0000 Subject: openconnect: T2036: Move CLI commands under vpn openconnect --- src/conf_mode/vpn_anyconnect.py | 135 ------------------------------------- src/conf_mode/vpn_openconnect.py | 135 +++++++++++++++++++++++++++++++++++++ src/op_mode/anyconnect-control.py | 67 ------------------ src/op_mode/openconnect-control.py | 67 ++++++++++++++++++ 4 files changed, 202 insertions(+), 202 deletions(-) delete mode 100755 src/conf_mode/vpn_anyconnect.py create mode 100755 src/conf_mode/vpn_openconnect.py delete mode 100755 src/op_mode/anyconnect-control.py create mode 100755 src/op_mode/openconnect-control.py (limited to 'src') diff --git a/src/conf_mode/vpn_anyconnect.py b/src/conf_mode/vpn_anyconnect.py deleted file mode 100755 index 158e1a117..000000000 --- a/src/conf_mode/vpn_anyconnect.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-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 . - -import os -from sys import exit - -from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.xml import defaults -from vyos.template import render -from vyos.util import call -from vyos import ConfigError -from crypt import crypt, mksalt, METHOD_SHA512 - -from vyos import airbag -airbag.enable() - -cfg_dir = '/run/ocserv' -ocserv_conf = cfg_dir + '/ocserv.conf' -ocserv_passwd = cfg_dir + '/ocpasswd' -radius_cfg = cfg_dir + '/radiusclient.conf' -radius_servers = cfg_dir + '/radius_servers' - - -# Generate hash from user cleartext password -def get_hash(password): - return crypt(password, mksalt(METHOD_SHA512)) - - -def get_config(): - conf = Config() - base = ['vpn', 'anyconnect'] - if not conf.exists(base): - return None - - ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - default_values = defaults(base) - ocserv = dict_merge(default_values, ocserv) - return ocserv - - -def verify(ocserv): - if ocserv is None: - return None - - # Check authentication - if "authentication" in ocserv: - if "mode" in ocserv["authentication"]: - if "local" in ocserv["authentication"]["mode"]: - if not ocserv["authentication"]["local_users"] or not ocserv["authentication"]["local_users"]["username"]: - raise ConfigError('Anyconect mode local required at leat one user') - else: - for user in ocserv["authentication"]["local_users"]["username"]: - if not "password" in ocserv["authentication"]["local_users"]["username"][user]: - raise ConfigError(f'password required for user {user}') - else: - raise ConfigError('anyconnect authentication mode required') - else: - raise ConfigError('anyconnect authentication credentials required') - - # Check ssl - if "ssl" in ocserv: - req_cert = ['ca_cert_file', 'cert_file', 'key_file'] - for cert in req_cert: - if not cert in ocserv["ssl"]: - raise ConfigError('anyconnect ssl {0} required'.format(cert.replace('_', '-'))) - else: - raise ConfigError('anyconnect ssl required') - - # Check network settings - if "network_settings" in ocserv: - if "push_route" in ocserv["network_settings"]: - # Replace default route - if "0.0.0.0/0" in ocserv["network_settings"]["push_route"]: - ocserv["network_settings"]["push_route"].remove("0.0.0.0/0") - ocserv["network_settings"]["push_route"].append("default") - else: - ocserv["network_settings"]["push_route"] = "default" - else: - raise ConfigError('anyconnect network settings required') - - -def generate(ocserv): - if not ocserv: - return None - - if "radius" in ocserv["authentication"]["mode"]: - # Render radius client configuration - render(radius_cfg, 'ocserv/radius_conf.tmpl', ocserv["authentication"]["radius"], trim_blocks=True) - # Render radius servers - render(radius_servers, 'ocserv/radius_servers.tmpl', ocserv["authentication"]["radius"], trim_blocks=True) - else: - if "local_users" in ocserv["authentication"]: - for user in ocserv["authentication"]["local_users"]["username"]: - ocserv["authentication"]["local_users"]["username"][user]["hash"] = get_hash(ocserv["authentication"]["local_users"]["username"][user]["password"]) - # Render local users - render(ocserv_passwd, 'ocserv/ocserv_passwd.tmpl', ocserv["authentication"]["local_users"], trim_blocks=True) - - # Render config - render(ocserv_conf, 'ocserv/ocserv_config.tmpl', ocserv, trim_blocks=True) - - - -def apply(ocserv): - if not ocserv: - call('systemctl stop ocserv.service') - for file in [ocserv_conf, ocserv_passwd]: - if os.path.exists(file): - os.unlink(file) - else: - call('systemctl restart ocserv.service') - - -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/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py new file mode 100755 index 000000000..af8604972 --- /dev/null +++ b/src/conf_mode/vpn_openconnect.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018-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 . + +import os +from sys import exit + +from vyos.config import Config +from vyos.configdict import dict_merge +from vyos.xml import defaults +from vyos.template import render +from vyos.util import call +from vyos import ConfigError +from crypt import crypt, mksalt, METHOD_SHA512 + +from vyos import airbag +airbag.enable() + +cfg_dir = '/run/ocserv' +ocserv_conf = cfg_dir + '/ocserv.conf' +ocserv_passwd = cfg_dir + '/ocpasswd' +radius_cfg = cfg_dir + '/radiusclient.conf' +radius_servers = cfg_dir + '/radius_servers' + + +# Generate hash from user cleartext password +def get_hash(password): + return crypt(password, mksalt(METHOD_SHA512)) + + +def get_config(): + conf = Config() + base = ['vpn', 'openconnect'] + if not conf.exists(base): + return None + + ocserv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + default_values = defaults(base) + ocserv = dict_merge(default_values, ocserv) + return ocserv + + +def verify(ocserv): + if ocserv is None: + return None + + # Check authentication + if "authentication" in ocserv: + if "mode" in ocserv["authentication"]: + if "local" in ocserv["authentication"]["mode"]: + if not ocserv["authentication"]["local_users"] or not ocserv["authentication"]["local_users"]["username"]: + raise ConfigError('openconnect mode local required at leat one user') + else: + for user in ocserv["authentication"]["local_users"]["username"]: + if not "password" in ocserv["authentication"]["local_users"]["username"][user]: + raise ConfigError(f'password required for user {user}') + else: + raise ConfigError('openconnect authentication mode required') + else: + raise ConfigError('openconnect authentication credentials required') + + # Check ssl + if "ssl" in ocserv: + req_cert = ['ca_cert_file', 'cert_file', 'key_file'] + for cert in req_cert: + if not cert in ocserv["ssl"]: + raise ConfigError('openconnect ssl {0} required'.format(cert.replace('_', '-'))) + else: + raise ConfigError('openconnect ssl required') + + # Check network settings + if "network_settings" in ocserv: + if "push_route" in ocserv["network_settings"]: + # Replace default route + if "0.0.0.0/0" in ocserv["network_settings"]["push_route"]: + ocserv["network_settings"]["push_route"].remove("0.0.0.0/0") + ocserv["network_settings"]["push_route"].append("default") + else: + ocserv["network_settings"]["push_route"] = "default" + else: + raise ConfigError('openconnect network settings required') + + +def generate(ocserv): + if not ocserv: + return None + + if "radius" in ocserv["authentication"]["mode"]: + # Render radius client configuration + render(radius_cfg, 'ocserv/radius_conf.tmpl', ocserv["authentication"]["radius"], trim_blocks=True) + # Render radius servers + render(radius_servers, 'ocserv/radius_servers.tmpl', ocserv["authentication"]["radius"], trim_blocks=True) + else: + if "local_users" in ocserv["authentication"]: + for user in ocserv["authentication"]["local_users"]["username"]: + ocserv["authentication"]["local_users"]["username"][user]["hash"] = get_hash(ocserv["authentication"]["local_users"]["username"][user]["password"]) + # Render local users + render(ocserv_passwd, 'ocserv/ocserv_passwd.tmpl', ocserv["authentication"]["local_users"], trim_blocks=True) + + # Render config + render(ocserv_conf, 'ocserv/ocserv_config.tmpl', ocserv, trim_blocks=True) + + + +def apply(ocserv): + if not ocserv: + call('systemctl stop ocserv.service') + for file in [ocserv_conf, ocserv_passwd]: + if os.path.exists(file): + os.unlink(file) + else: + call('systemctl restart ocserv.service') + + +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/op_mode/anyconnect-control.py b/src/op_mode/anyconnect-control.py deleted file mode 100755 index 6382016b7..000000000 --- a/src/op_mode/anyconnect-control.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/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 . - -import sys -import argparse -import json - -from vyos.config import Config -from vyos.util import popen, run, DEVNULL -from tabulate import tabulate - -occtl = '/usr/bin/occtl' -occtl_socket = '/run/ocserv/occtl.socket' - -def show_sessions(): - out, code = popen("sudo {0} -j -s {1} show users".format(occtl, occtl_socket),stderr=DEVNULL) - if code: - sys.exit('Cannot get anyconnect users information') - else: - headers = ["interface", "username", "ip", "remote IP", "RX", "TX", "state", "uptime"] - sessions = json.loads(out) - ses_list = [] - for ses in sessions: - ses_list.append([ses["Device"], ses["Username"], ses["IPv4"], ses["Remote IP"], ses["_RX"], ses["_TX"], ses["State"], ses["_Connected at"]]) - if len(ses_list) > 0: - print(tabulate(ses_list, headers)) - else: - print("No active anyconnect sessions") - -def is_ocserv_configured(): - if not Config().exists_effective('vpn anyconnect'): - print("vpn anyconnect server is not configured") - sys.exit(1) - -def main(): - #parese args - parser = argparse.ArgumentParser() - parser.add_argument('--action', help='Control action', required=True) - parser.add_argument('--selector', help='Selector username|ifname|sid', required=False) - parser.add_argument('--target', help='Target must contain username|ifname|sid', required=False) - args = parser.parse_args() - - - # Check is IPoE configured - is_ocserv_configured() - - if args.action == "restart": - run("systemctl restart ocserv") - sys.exit(0) - elif args.action == "show_sessions": - show_sessions() - -if __name__ == '__main__': - main() diff --git a/src/op_mode/openconnect-control.py b/src/op_mode/openconnect-control.py new file mode 100755 index 000000000..ef9fe618c --- /dev/null +++ b/src/op_mode/openconnect-control.py @@ -0,0 +1,67 @@ +#!/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 . + +import sys +import argparse +import json + +from vyos.config import Config +from vyos.util import popen, run, DEVNULL +from tabulate import tabulate + +occtl = '/usr/bin/occtl' +occtl_socket = '/run/ocserv/occtl.socket' + +def show_sessions(): + out, code = popen("sudo {0} -j -s {1} show users".format(occtl, occtl_socket),stderr=DEVNULL) + if code: + sys.exit('Cannot get openconnect users information') + else: + headers = ["interface", "username", "ip", "remote IP", "RX", "TX", "state", "uptime"] + sessions = json.loads(out) + ses_list = [] + for ses in sessions: + ses_list.append([ses["Device"], ses["Username"], ses["IPv4"], ses["Remote IP"], ses["_RX"], ses["_TX"], ses["State"], ses["_Connected at"]]) + if len(ses_list) > 0: + print(tabulate(ses_list, headers)) + else: + print("No active openconnect sessions") + +def is_ocserv_configured(): + if not Config().exists_effective('vpn openconnect'): + print("vpn openconnect server is not configured") + sys.exit(1) + +def main(): + #parese args + parser = argparse.ArgumentParser() + parser.add_argument('--action', help='Control action', required=True) + parser.add_argument('--selector', help='Selector username|ifname|sid', required=False) + parser.add_argument('--target', help='Target must contain username|ifname|sid', required=False) + args = parser.parse_args() + + + # Check is Openconnect server configured + is_ocserv_configured() + + if args.action == "restart": + run("systemctl restart ocserv") + sys.exit(0) + elif args.action == "show_sessions": + show_sessions() + +if __name__ == '__main__': + main() -- cgit v1.2.3 From c3d170b17e39e94e6f53e4afd8d0468d35e9d8fc Mon Sep 17 00:00:00 2001 From: sever-sever Date: Thu, 10 Sep 2020 07:16:39 +0000 Subject: op-mode: T2856: Fix broken pipe in show version all --- op-mode-definitions/show-version.xml | 2 +- src/op_mode/show_version.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) (limited to 'src') diff --git a/op-mode-definitions/show-version.xml b/op-mode-definitions/show-version.xml index aae5bb008..905a4865c 100644 --- a/op-mode-definitions/show-version.xml +++ b/op-mode-definitions/show-version.xml @@ -18,7 +18,7 @@ Show system version and versions of all packages - ${vyos_op_scripts_dir}/show_version.py --all + echo "Package versions:"; dpkg -l | awk '$0~/>/{exit}1' diff --git a/src/op_mode/show_version.py b/src/op_mode/show_version.py index d0d5c6785..5bbc2e1f1 100755 --- a/src/op_mode/show_version.py +++ b/src/op_mode/show_version.py @@ -27,7 +27,6 @@ from sys import exit from vyos.util import call parser = argparse.ArgumentParser() -parser.add_argument("-a", "--all", action="store_true", help="Include individual package versions") parser.add_argument("-f", "--funny", action="store_true", help="Add something funny to the output") parser.add_argument("-j", "--json", action="store_true", help="Produce JSON output") @@ -65,9 +64,5 @@ if __name__ == '__main__': tmpl = Template(version_output_tmpl) print(tmpl.render(version_data)) - if args.all: - print("Package versions:") - call("dpkg -l") - if args.funny: print(vyos.limericks.get_random()) -- cgit v1.2.3 From 5f7f976d15be664e9ac29a46dc33cc3a9c3572fd Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 13 Sep 2020 10:30:24 +0200 Subject: configd: T2582: add .gitignore --- src/shim/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/shim/.gitignore (limited to 'src') diff --git a/src/shim/.gitignore b/src/shim/.gitignore new file mode 100644 index 000000000..d33538138 --- /dev/null +++ b/src/shim/.gitignore @@ -0,0 +1,2 @@ +/mkjson/obj/ +/vyshim -- cgit v1.2.3 From 56bb811e1ce626aa783ffafe9fe8952da9bda82d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 13 Sep 2020 10:32:26 +0200 Subject: qat: T2857: cleanup configuration script --- src/conf_mode/intel_qat.py | 145 +++++++++++++++++++++------------------------ 1 file changed, 69 insertions(+), 76 deletions(-) (limited to 'src') diff --git a/src/conf_mode/intel_qat.py b/src/conf_mode/intel_qat.py index 1e5101a9f..86dbccaf0 100755 --- a/src/conf_mode/intel_qat.py +++ b/src/conf_mode/intel_qat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-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 @@ -13,94 +13,87 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -# -import sys import os import re +from sys import exit + from vyos.config import Config -from vyos import ConfigError from vyos.util import popen, run - +from vyos import ConfigError from vyos import airbag airbag.enable() -# Define for recovering -gl_ipsec_conf = None +qat_init_script = '/etc/init.d/qat_service' def get_config(config=None): - if config: - c = config - else: - c = Config() - config_data = { - 'qat_conf' : None, - 'ipsec_conf' : None, - 'openvpn_conf' : None, - } - - if c.exists('system acceleration qat'): - config_data['qat_conf'] = True - - if c.exists('vpn ipsec '): - gl_ipsec_conf = True - config_data['ipsec_conf'] = True - - if c.exists('interfaces openvpn'): - config_data['openvpn_conf'] = True - - return config_data - -# Control configured VPN service which can use QAT -def vpn_control(action): - # XXX: Should these commands report failure - if action == 'restore' and gl_ipsec_conf: - return run('ipsec start') - return run(f'ipsec {action}') - -def verify(c): - # Check if QAT service installed - if not os.path.exists('/etc/init.d/qat_service'): - raise ConfigError("Warning: QAT init file not found") - - if c['qat_conf'] == None: - return - - # Check if QAT device exist - output, err = popen('lspci -nn', decode='utf-8') - if not err: - data = re.findall('(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)', output) - #If QAT devices found - if not data: - print("\t No QAT acceleration device found") - sys.exit(1) - -def apply(c): - if c['ipsec_conf']: + if config: + conf = config + else: + conf = Config() + + data = {} + + if conf.exists(['system', 'acceleration', 'qat']): + data.update({'qat_enable' : ''}) + + if conf.exists(['vpn', 'ipsec']): + data.update({'ipsec' : ''}) + + if conf.exists(['interfaces', 'openvpn']): + data.update({'openvpn' : ''}) + + return data + + +def vpn_control(action, force_ipsec=False): + # XXX: Should these commands report failure? + if action == 'restore' and force_ipsec: + return run('ipsec start') + + return run(f'ipsec {action}') + + +def verify(qat): + if 'qat_enable' not in qat: + return + + # Check if QAT service installed + if not os.path.exists(qat_init_script): + raise ConfigError('QAT init script not found') + + # Check if QAT device exist + output, err = popen('lspci -nn', decode='utf-8') + if not err: + data = re.findall( + '(8086:19e2)|(8086:37c8)|(8086:0435)|(8086:6f54)', output) + # If QAT devices found + if not data: + raise ConfigError('No QAT acceleration device found') + +def apply(qat): # Shutdown VPN service which can use QAT - vpn_control('stop') + if 'ipsec' in qat: + vpn_control('stop') + + # Enable/Disable QAT service + if 'qat_enable' in qat: + run(f'{qat_init_script} start') + else: + run(f'{qat_init_script} stop') - # Disable QAT service - if c['qat_conf'] == None: - run('/etc/init.d/qat_service stop') - if c['ipsec_conf']: - vpn_control('start') - return + # Recover VPN service + if 'ipsec' in qat: + vpn_control('start') - # Run qat init.d script - run('/etc/init.d/qat_service start') - if c['ipsec_conf']: - # Recovery VPN service - vpn_control('start') if __name__ == '__main__': - try: - c = get_config() - verify(c) - apply(c) - except ConfigError as e: - print(e) - vpn_control('restore') - sys.exit(1) + try: + c = get_config() + verify(c) + apply(c) + except ConfigError as e: + print(e) + vpn_control('restore', force_ipsec=('ipsec' in c)) + exit(1) -- cgit v1.2.3 From 25136d9a9501dcc40c31f9db8e90be3eb5569d24 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 13 Sep 2020 13:17:03 +0200 Subject: ddclient: T2858: migrate to get_config_dict() --- data/templates/dynamic-dns/ddclient.conf.tmpl | 73 ++++----- interface-definitions/dns-dynamic.xml.in | 1 + src/conf_mode/dynamic_dns.py | 217 +++++++------------------- 3 files changed, 99 insertions(+), 192 deletions(-) (limited to 'src') diff --git a/data/templates/dynamic-dns/ddclient.conf.tmpl b/data/templates/dynamic-dns/ddclient.conf.tmpl index 9c7219230..6fbbb50c3 100644 --- a/data/templates/dynamic-dns/ddclient.conf.tmpl +++ b/data/templates/dynamic-dns/ddclient.conf.tmpl @@ -3,44 +3,47 @@ daemon=1m syslog=yes ssl=yes -{% for interface in interfaces -%} +{% for iface in interface %} +# ddclient configuration for interface "{{ iface }}" +{% if interface[iface].use_web is defined and interface[iface].use_web is not none %} +{% set web_skip = ", web-skip='" + interface[iface].use_web.skip + "'" if interface[iface].use_web.skip is defined else '' %} +use=web, web='{{ interface[iface].use_web.url }}'{{ web_skip }} +{% else %} +use=if, if={{ iface }} +{% endif %} -# -# ddclient configuration for interface "{{ interface.interface }}": -# -{% if interface.web_url -%} -use=web, web='{{ interface.web_url}}' {%- if interface.web_skip %}, web-skip='{{ interface.web_skip }}'{% endif %} -{% else -%} -use=if, if={{ interface.interface }} -{% endif -%} - -{% for rfc in interface.rfc2136 -%} -{% for record in rfc.record %} -# RFC2136 dynamic DNS configuration for {{ record }}.{{ rfc.zone }} -server={{ rfc.server }} +{% if interface[iface].rfc2136 is defined and interface[iface].rfc2136 is not none %} +{% for rfc2136, config in interface[iface].rfc2136.items() %} +{% for dns_record in config.record if config.record is defined %} +# RFC2136 dynamic DNS configuration for {{ rfc2136 }}, {{ config.zone }}, {{ dns_record }} +server={{ config.server }} protocol=nsupdate -password={{ rfc.keyfile }} -ttl={{ rfc.ttl }} -zone={{ rfc.zone }} -{{ record }} -{% endfor -%} -{% endfor -%} +password={{ config.keyfile }} +ttl={{ config.ttl }} +zone={{ config.zone }} +{{ dns_record }} + +{% endfor %} +{% endfor %} +{% endif %} -{% for srv in interface.service %} -{% for host in srv.host %} -# DynDNS provider configuration for {{ host }} -protocol={{ srv.protocol }}, +{% if interface[iface].service is defined and interface[iface].service is not none %} +{% for service, config in interface[iface].service.items() %} +{% for dns_record in config.host_name %} +# DynDNS provider configuration for {{ service }}, {{ dns_record }} +protocol={{ config.protocol }}, max-interval=28d, -login={{ srv.login }}, -password='{{ srv.password }}', -{% if srv.server -%} -server={{ srv.server }}, -{% endif -%} -{% if srv.zone -%} -zone={{ srv.zone }}, -{% endif -%} -{{ host }} -{% endfor %} -{% endfor %} +login={{ config.login }}, +password='{{ config.password }}', +{% if config.server %} +server={{ config.server }}, +{% endif %} +{% if config.zone %} +zone={{ config.zone }}, +{% endif %} +{{ dns_record }} +{% endfor %} +{% endfor %} +{% endif %} {% endfor %} diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in index 143c04ef6..34a31a7c5 100644 --- a/interface-definitions/dns-dynamic.xml.in +++ b/interface-definitions/dns-dynamic.xml.in @@ -58,6 +58,7 @@ + 600 diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index 57c910a68..93e995b78 100755 --- a/src/conf_mode/dynamic_dns.py +++ b/src/conf_mode/dynamic_dns.py @@ -17,14 +17,13 @@ import os from sys import exit -from copy import deepcopy -from stat import S_IRUSR, S_IWUSR from vyos.config import Config -from vyos import ConfigError -from vyos.util import call +from vyos.configdict import dict_merge from vyos.template import render - +from vyos.util import call +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() @@ -45,197 +44,101 @@ default_service_protocol = { 'zoneedit': 'zoneedit1' } -default_config_data = { - 'interfaces': [], - 'deleted': False -} - def get_config(config=None): - dyndns = deepcopy(default_config_data) if config: conf = config else: conf = Config() - base_level = ['service', 'dns', 'dynamic'] + base_level = ['service', 'dns', 'dynamic'] if not conf.exists(base_level): - dyndns['deleted'] = True - return dyndns - - for interface in conf.list_nodes(base_level + ['interface']): - node = { - 'interface': interface, - 'rfc2136': [], - 'service': [], - 'web_skip': '', - 'web_url': '' - } - - # set config level to e.g. "service dns dynamic interface eth0" - conf.set_level(base_level + ['interface', interface]) - # Handle RFC2136 - Dynamic Updates in the Domain Name System - for rfc2136 in conf.list_nodes(['rfc2136']): - rfc = { - 'name': rfc2136, - 'keyfile': '', - 'record': [], - 'server': '', - 'ttl': '600', - 'zone': '' - } - - # set config level - conf.set_level(base_level + ['interface', interface, 'rfc2136', rfc2136]) - - if conf.exists(['key']): - rfc['keyfile'] = conf.return_value(['key']) - - if conf.exists(['record']): - rfc['record'] = conf.return_values(['record']) - - if conf.exists(['server']): - rfc['server'] = conf.return_value(['server']) - - if conf.exists(['ttl']): - rfc['ttl'] = conf.return_value(['ttl']) - - if conf.exists(['zone']): - rfc['zone'] = conf.return_value(['zone']) - - node['rfc2136'].append(rfc) - - # set config level to e.g. "service dns dynamic interface eth0" - conf.set_level(base_level + ['interface', interface]) - # Handle DynDNS service providers - for service in conf.list_nodes(['service']): - srv = { - 'provider': service, - 'host': [], - 'login': '', - 'password': '', - 'protocol': '', - 'server': '', - 'custom' : False, - 'zone' : '' - } - - # set config level - conf.set_level(base_level + ['interface', interface, 'service', service]) - - # preload protocol from default service mapping - if service in default_service_protocol.keys(): - srv['protocol'] = default_service_protocol[service] - else: - srv['custom'] = True - - if conf.exists(['login']): - srv['login'] = conf.return_value(['login']) - - if conf.exists(['host-name']): - srv['host'] = conf.return_values(['host-name']) - - if conf.exists(['protocol']): - srv['protocol'] = conf.return_value(['protocol']) - - if conf.exists(['password']): - srv['password'] = conf.return_value(['password']) - - if conf.exists(['server']): - srv['server'] = conf.return_value(['server']) - - if conf.exists(['zone']): - srv['zone'] = conf.return_value(['zone']) - elif srv['provider'] == 'cloudflare': - # default populate zone entry with bar.tld if - # host-name is foo.bar.tld - srv['zone'] = srv['host'][0].split('.',1)[1] - - node['service'].append(srv) - - # Set config back to appropriate level for these options - conf.set_level(base_level + ['interface', interface]) - - # Additional settings in CLI - if conf.exists(['use-web', 'skip']): - node['web_skip'] = conf.return_value(['use-web', 'skip']) - - if conf.exists(['use-web', 'url']): - node['web_url'] = conf.return_value(['use-web', 'url']) - - # set config level back to top level - conf.set_level(base_level) - - dyndns['interfaces'].append(node) + return None + + dyndns = conf.get_config_dict(base_level, 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. + for interface in dyndns['interface']: + if 'service' in dyndns['interface'][interface]: + # 'Autodetect' protocol used by DynDNS service + for service in dyndns['interface'][interface]['service']: + if service in default_service_protocol: + dyndns['interface'][interface]['service'][service].update( + {'protocol' : default_service_protocol.get(service)}) + else: + dyndns['interface'][interface]['service'][service].update( + {'custom': ''}) + + if 'rfc2136' in dyndns['interface'][interface]: + default_values = defaults(base_level + ['interface', 'rfc2136']) + for rfc2136 in dyndns['interface'][interface]['rfc2136']: + dyndns['interface'][interface]['rfc2136'][rfc2136] = dict_merge( + default_values, dyndns['interface'][interface]['rfc2136'][rfc2136]) return dyndns def verify(dyndns): # bail out early - looks like removal from running config - if dyndns['deleted']: + if not dyndns: return None # A 'node' corresponds to an interface - for node in dyndns['interfaces']: + if 'interface' not in dyndns: + return None + for interface in dyndns['interface']: # RFC2136 - configuration validation - for rfc2136 in node['rfc2136']: - if not rfc2136['record']: - raise ConfigError('Set key for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface'])) + if 'rfc2136' in dyndns['interface'][interface]: + for rfc2136, config in dyndns['interface'][interface]['rfc2136'].items(): - if not rfc2136['zone']: - raise ConfigError('Set zone for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface'])) + for tmp in ['record', 'zone', 'server', 'key']: + if tmp not in config: + raise ConfigError(f'"{tmp}" required for rfc2136 based ' + f'DynDNS service on "{interface}"') - if not rfc2136['keyfile']: - raise ConfigError('Set keyfile for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface'])) - else: - if not os.path.isfile(rfc2136['keyfile']): - raise ConfigError('Keyfile for service "{0}" to send DDNS updates for interface "{1}" does not exist'.format(rfc2136['name'], node['interface'])) - - if not rfc2136['server']: - raise ConfigError('Set server for service "{0}" to send DDNS updates for interface "{1}"'.format(rfc2136['name'], node['interface'])) + if not os.path.isfile(config['key']): + raise ConfigError(f'"key"-file not found for rfc2136 based ' + f'DynDNS service on "{interface}"') # DynDNS service provider - configuration validation - for service in node['service']: - if not service['host']: - raise ConfigError('Set host-name for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface'])) + if 'service' in dyndns['interface'][interface]: + for service, config in dyndns['interface'][interface]['service'].items(): + error_msg = f'required for DynDNS service "{service}" on "{interface}"' + if 'host_name' not in config: + raise ConfigError(f'"host-name" {error_msg}') - if not service['login']: - raise ConfigError('Set login for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface'])) + if 'login' not in config: + raise ConfigError(f'"login" (username) {error_msg}') - if not service['password']: - raise ConfigError('Set password for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface'])) + if 'password' not in config: + raise ConfigError(f'"password" {error_msg}') - if service['custom'] is True: - if not service['protocol']: - raise ConfigError('Set protocol for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface'])) + if 'zone' in config: + if service != 'cloudflare': + raise ConfigError(f'"zone" option only supported with CloudFlare') - if not service['server']: - raise ConfigError('Set server for service "{0}" to send DDNS updates for interface "{1}"'.format(service['provider'], node['interface'])) + if 'custom' in config: + if 'protocol' not in config: + raise ConfigError(f'"protocol" {error_msg}') - if service['zone']: - if service['provider'] != 'cloudflare': - raise ConfigError('Zone option not allowed for "{0}", it can only be used for CloudFlare'.format(service['provider'])) + if 'server' not in config: + raise ConfigError(f'"server" {error_msg}') return None def generate(dyndns): # bail out early - looks like removal from running config - if dyndns['deleted']: + if not dyndns: return None - render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns) - - # Config file must be accessible only by its owner - os.chmod(config_file, S_IRUSR | S_IWUSR) - + render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns, trim_blocks=True, permission=0o600) return None def apply(dyndns): - if dyndns['deleted']: + if not dyndns: call('systemctl stop ddclient.service') if os.path.exists(config_file): os.unlink(config_file) - else: call('systemctl restart ddclient.service') -- cgit v1.2.3 From 8ae88b5ba5cbc34b9992ccdde4229d44cfe56225 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 13 Sep 2020 17:28:42 +0200 Subject: op-mode: T2841: support IPv6 for "monitor bandwidth-test initiate" --- op-mode-definitions/monitor-bandwidth-test.xml | 2 +- src/op_mode/monitor_bandwidth_test.sh | 30 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100755 src/op_mode/monitor_bandwidth_test.sh (limited to 'src') diff --git a/op-mode-definitions/monitor-bandwidth-test.xml b/op-mode-definitions/monitor-bandwidth-test.xml index 5959e05f2..5b36b1da5 100644 --- a/op-mode-definitions/monitor-bandwidth-test.xml +++ b/op-mode-definitions/monitor-bandwidth-test.xml @@ -20,7 +20,7 @@ <hostname> <x.x.x.x> <h:h:h:h:h:h:h:h> - iperf -c $4 + ${vyos_op_scripts_dir}/monitor_bandwidth_test.sh "$4" diff --git a/src/op_mode/monitor_bandwidth_test.sh b/src/op_mode/monitor_bandwidth_test.sh new file mode 100755 index 000000000..6da0291c5 --- /dev/null +++ b/src/op_mode/monitor_bandwidth_test.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# 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 . + +if ipaddrcheck --is-ipv6 $1; then + # Set address family to IPv6 when an IPv6 address was specified + OPT="-V" +elif [[ $(dig $1 AAAA +short | grep -v '\.$' | wc -l) -gt 0 ]]; then + # CNAME is also part of the dig answer thus we must remove any + # CNAME response and only shot the AAAA response(s), this is done + # by grep -v '\.$' + + # Set address family to IPv6 when FQDN has at least one AAAA record + OPT="-V" +fi + +/usr/bin/iperf $OPT -c $1 + -- cgit v1.2.3 From b82871584b2a087b5b690f8eace1e99b7c948cf3 Mon Sep 17 00:00:00 2001 From: sever-sever Date: Mon, 14 Sep 2020 07:27:48 +0000 Subject: op-mode: T2874: Add new utill for mtu-check --- op-mode-definitions/force-mtu-host.xml | 34 ++++++++++++++++++++++ src/op_mode/force_mtu_host.sh | 52 ++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 op-mode-definitions/force-mtu-host.xml create mode 100755 src/op_mode/force_mtu_host.sh (limited to 'src') diff --git a/op-mode-definitions/force-mtu-host.xml b/op-mode-definitions/force-mtu-host.xml new file mode 100644 index 000000000..b92179f11 --- /dev/null +++ b/op-mode-definitions/force-mtu-host.xml @@ -0,0 +1,34 @@ + + + + + + + Show MTU max value for remote host protocol TCP + + + + + IP address of the remote host + + <hostname> <x.x.x.x> <h:h:h:h:h:h:h:h> + + + ${vyos_op_scripts_dir}/force_mtu_host.sh $4 + + + + Source interface + + + + + ${vyos_op_scripts_dir}/force_mtu_host.sh $4 $6 + + + + + + + + diff --git a/src/op_mode/force_mtu_host.sh b/src/op_mode/force_mtu_host.sh new file mode 100755 index 000000000..02955c729 --- /dev/null +++ b/src/op_mode/force_mtu_host.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# +# Module: vyos-show-ram.sh +# Displays memory usage information in minimalistic format +# +# 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 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 . + +target=$1 +interface=$2 + +# IPv4 header 20 byte + TCP header 20 byte +ipv4_overhead=40 + +# IPv6 headter 40 byte + TCP header 20 byte +ipv6_overhead=60 + +# If no arguments +if [[ $# -eq 0 ]] ; then + echo "Target host not defined" + exit 1 +fi + +# If one argument, it's ip address. If 2, the second arg "interface" +if [[ $# -eq 1 ]] ; then + mtu=$(sudo nmap -T4 --script path-mtu -F $target | grep "PMTU" | awk {'print $NF'}) +elif [[ $# -eq 2 ]]; then + mtu=$(sudo nmap -T4 -e $interface --script path-mtu -F $target | grep "PMTU" | awk {'print $NF'}) +fi + +tcpv4_mss=$(($mtu-$ipv4_overhead)) +tcpv6_mss=$(($mtu-$ipv6_overhead)) + +echo " +Recommended maximum values (or less) for target $target: +--- +MTU: $mtu +TCP-MSS: $tcpv4_mss +TCP-MSS_IPv6: $tcpv6_mss +" + -- cgit v1.2.3 From bf1d6fff80eebb579f2c33b1352a7162b8474730 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 14 Sep 2020 15:41:22 -0500 Subject: configd: T2885: print commit errors to config session terminal --- src/services/vyos-configd | 19 +++++++++++++++++++ src/shim/vyshim.c | 10 ++++++++++ 2 files changed, 29 insertions(+) (limited to 'src') diff --git a/src/services/vyos-configd b/src/services/vyos-configd index 75f84d3df..579605e8c 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -62,6 +62,8 @@ configd_env_file = '/etc/default/vyos-configd-env' active_string = '' session_string = '' +session_tty = None + def key_name_from_file_name(f): return os.path.splitext(f)[0] @@ -105,6 +107,13 @@ conf_mode_scripts = dict(zip(imports, modules)) exclude_set = {key_name_from_file_name(f) for f in filenames if f not in include} include_set = {key_name_from_file_name(f) for f in filenames if f in include} +def explicit_print(t, m): + try: + with open(t, 'w') as f: + f.write(m) + f.flush() + except Exception: + pass def run_script(script, config) -> int: config.set_level([]) @@ -115,6 +124,7 @@ def run_script(script, config) -> int: script.apply(c) except ConfigError as e: logger.critical(e) + explicit_print(session_tty, e) return R_ERROR_COMMIT except Exception: return R_ERROR_DAEMON @@ -132,6 +142,15 @@ def initialization(socket): session_string = socket.recv().decode() resp = "session" socket.send(resp.encode()) + pid_string = socket.recv().decode() + resp = "pid" + socket.send(resp.encode()) + + logger.debug(f"config session pid is {pid_string}") + try: + session_tty = os.readlink(f"/proc/{pid_string}/fd/1") + except FileNotFoundError: + session_tty = None configsource = ConfigSourceString(running_config_text=active_string, session_config_text=session_string) diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c index 8b6feab99..196e3221e 100644 --- a/src/shim/vyshim.c +++ b/src/shim/vyshim.c @@ -162,6 +162,10 @@ int initialization(void* Requester) double prev_time_value, time_value; double time_diff; + char *pid_val = getenv("VYATTA_CONFIG_TMP"); + strsep(&pid_val, "_"); + debug_print("config session pid: %s\n", pid_val); + debug_print("Sending init announcement\n"); char *init_announce = mkjson(MKJSON_OBJ, 1, MKJSON_STRING, "type", "init"); @@ -219,6 +223,12 @@ int initialization(void* Requester) free(session_str); + debug_print("Sending config session pid\n"); + zmq_send(Requester, pid_val, strlen(pid_val), 0); + zmq_recv(Requester, buffer, 16, 0); + debug_print("Received pid receipt\n"); + + return 0; } -- cgit v1.2.3 From f8a6fa6a5a574851292e77e08cff16cdf6195334 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 15 Sep 2020 18:52:18 +0200 Subject: vyos.configdict: T2515: leaf_node_changed() should return list or None --- python/vyos/configdict.py | 6 +++--- src/conf_mode/interfaces-l2tpv3.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index e8c0aa5b3..bfc70b772 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -148,8 +148,8 @@ def T2665_default_dict_cleanup(dict): def leaf_node_changed(conf, path): """ Check if a leaf node was altered. If it has been altered - values has been - changed, or it was added/removed, we will return the old value. If nothing - has been changed, None is returned + changed, or it was added/removed, we will return a list containing the old + value(s). If nothing has been changed, None is returned """ from vyos.configdiff import get_config_diff D = get_config_diff(conf, key_mangling=('-', '_')) @@ -157,7 +157,7 @@ def leaf_node_changed(conf, path): (new, old) = D.get_value_diff(path) if new != old: if isinstance(old, str): - return old + return [old] elif isinstance(old, list): if isinstance(new, str): new = [new] diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 8250a3df8..144cee5fe 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -56,10 +56,11 @@ def get_config(config=None): # To delete an l2tpv3 interface we need the current tunnel and session-id if 'deleted' in l2tpv3: tmp = leaf_node_changed(conf, ['tunnel-id']) - l2tpv3.update({'tunnel_id': tmp}) + # leaf_node_changed() returns a list + l2tpv3.update({'tunnel_id': tmp[0]}) tmp = leaf_node_changed(conf, ['session-id']) - l2tpv3.update({'session_id': tmp}) + l2tpv3.update({'session_id': tmp[0]}) return l2tpv3 -- cgit v1.2.3 From 98d95b677867c27064d84033dc451ba04c9a2b7b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 15 Sep 2020 18:54:09 +0200 Subject: bonding: T2515: preserve interface admin state when removing from bond Removing a member from a bond/LACP will turn the physical interface always in admin-down state. This is invalid, the interface should be placed into the state configured on the VyOS CLI. Smoketest on bond interfaces is extended to check this behavior. --- python/vyos/ifconfig/bond.py | 22 ++++++++----- smoketest/scripts/cli/test_interfaces_bonding.py | 24 ++++++++++++++ src/conf_mode/interfaces-bonding.py | 42 +++++++++++++----------- 3 files changed, 59 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 67dcd2b69..c33cf30bf 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -1,4 +1,4 @@ -# Copyright 2019 VyOS maintainers and contributors +# Copyright 2019-2020 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -381,9 +381,14 @@ class BondIf(Interface): # Some interface options can only be changed if the interface is # administratively down if self.get_admin_state() == 'down': - # Delete bond member port(s) + # Remove ALL bond member interfaces for interface in self.get_slaves(): self.del_port(interface) + # Removing an interface from a bond will always place the underlaying + # physical interface in admin-down state! If physical interface is + # not disabled, re-enable it. + if not vyos_dict_search(f'member.interface_remove.{interface}.disable', config): + Interface(interface).set_admin_state('up') # Bonding policy/mode value = config.get('mode') @@ -391,13 +396,12 @@ class BondIf(Interface): # Add (enslave) interfaces to bond value = vyos_dict_search('member.interface', config) - if value: - for interface in value: - # if we've come here we already verified the interface - # does not have an addresses configured so just flush - # any remaining ones - Interface(interface).flush_addrs() - self.add_port(interface) + for interface in (value or []): + # if we've come here we already verified the interface + # does not have an addresses configured so just flush + # any remaining ones + Interface(interface).flush_addrs() + self.add_port(interface) # Primary device interface - must be set after 'mode' value = config.get('primary') diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index e3d3b25ee..b165883b9 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -20,6 +20,7 @@ import unittest from base_interfaces_test import BasicInterfaceTest from vyos.ifconfig import Section +from vyos.ifconfig.interface import Interface from vyos.configsession import ConfigSessionError from vyos.util import read_file @@ -57,5 +58,28 @@ class BondingInterfaceTest(BasicInterfaceTest.BaseTest): slaves = read_file(f'/sys/class/net/{interface}/bonding/slaves').split() self.assertListEqual(slaves, self._members) + def test_remove_member(self): + """ T2515: when removing a bond member the interface must be admin-up again """ + + # configure member interfaces + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.session.set(self._base_path + [interface] + option.split()) + + self.session.commit() + + # remove single bond member port + for interface in self._interfaces: + remove_member = self._members[0] + self.session.delete(self._base_path + [interface, 'member', 'interface', remove_member]) + + self.session.commit() + + # removed member port must be admin-up + for interface in self._interfaces: + remove_member = self._members[0] + state = Interface(remove_member).get_admin_state() + self.assertEqual('up', state) + if __name__ == '__main__': unittest.main() diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 16e6e4f6e..a9679b47c 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -29,6 +29,7 @@ from vyos.configverify import verify_source_interface from vyos.configverify import verify_vlan_config from vyos.configverify import verify_vrf from vyos.ifconfig import BondIf +from vyos.ifconfig import Section from vyos.validate import is_member from vyos.validate import has_address_configured from vyos import ConfigError @@ -69,31 +70,33 @@ def get_config(config=None): # into a dictionary - we will use this to add additional information # later on for wach member if 'member' in bond and 'interface' in bond['member']: - # first convert it to a list if only one member is given - if isinstance(bond['member']['interface'], str): - bond['member']['interface'] = [bond['member']['interface']] - - tmp={} - for interface in bond['member']['interface']: - tmp.update({interface: {}}) - - bond['member']['interface'] = tmp + # convert list if member interfaces to a dictionary + bond['member']['interface'] = dict.fromkeys( + bond['member']['interface'], {}) if 'mode' in bond: bond['mode'] = get_bond_mode(bond['mode']) tmp = leaf_node_changed(conf, ['mode']) - if tmp: - bond.update({'shutdown_required': ''}) + if tmp: bond.update({'shutdown_required': {}}) # determine which members have been removed - tmp = leaf_node_changed(conf, ['member', 'interface']) - if tmp: - bond.update({'shutdown_required': ''}) - if 'member' in bond: - bond['member'].update({'interface_remove': tmp }) - else: - bond.update({'member': {'interface_remove': tmp }}) + interfaces_removed = leaf_node_changed(conf, ['member', 'interface']) + if interfaces_removed: + bond.update({'shutdown_required': {}}) + if 'member' not in bond: + bond.update({'member': {}}) + + tmp = {} + for interface in interfaces_removed: + section = Section.section(interface) # this will be 'ethernet' for 'eth0' + if conf.exists(['insterfaces', section, interface, 'disable']): + tmp.update({interface : {'disable': ''}}) + else: + tmp.update({interface : {}}) + + # also present the interfaces to be removed from the bond as dictionary + bond['member'].update({'interface_remove': tmp}) if 'member' in bond and 'interface' in bond['member']: for interface, interface_config in bond['member']['interface'].items(): @@ -109,8 +112,7 @@ def get_config(config=None): # bond members must not have an assigned address tmp = has_address_configured(conf, interface) - if tmp: - interface_config.update({'has_address' : ''}) + if tmp: interface_config.update({'has_address' : ''}) return bond -- cgit v1.2.3 From 40ca599350731a743a0d999205df10829017a783 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 15 Sep 2020 18:55:46 +0200 Subject: completion: T2238: add license --- src/completion/list_interfaces.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/completion/list_interfaces.py b/src/completion/list_interfaces.py index e27281433..b19b90156 100755 --- a/src/completion/list_interfaces.py +++ b/src/completion/list_interfaces.py @@ -1,16 +1,28 @@ #!/usr/bin/env python3 +# +# Copyright (C) 2019-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 . import sys import argparse from vyos.ifconfig import Section - def matching(feature): for section in Section.feature(feature): for intf in Section.interfaces(section): yield intf - parser = argparse.ArgumentParser() group = parser.add_mutually_exclusive_group() group.add_argument("-t", "--type", type=str, help="List interfaces of specific type") -- cgit v1.2.3 From 6ac916ecd58cf1515dd5b9b47283d5528d0c265b Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 16 Sep 2020 11:24:16 -0500 Subject: configd: T2885: fix output of error string to config session --- src/services/vyos-configd | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/services/vyos-configd b/src/services/vyos-configd index 579605e8c..642952936 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -111,6 +111,7 @@ def explicit_print(t, m): try: with open(t, 'w') as f: f.write(m) + f.write("\n") f.flush() except Exception: pass @@ -124,7 +125,7 @@ def run_script(script, config) -> int: script.apply(c) except ConfigError as e: logger.critical(e) - explicit_print(session_tty, e) + explicit_print(session_tty, str(e)) return R_ERROR_COMMIT except Exception: return R_ERROR_DAEMON @@ -132,6 +133,7 @@ def run_script(script, config) -> int: return R_SUCCESS def initialization(socket): + global session_tty # Reset config strings: active_string = '' session_string = '' -- cgit v1.2.3 From aca23987aaa42bebe8950cf1a36ea3f0e4ee47a9 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 16 Sep 2020 21:27:02 +0200 Subject: wireless: T2887: Jinja2 can not work on keys starting with a number ... an error would be presented: jinja2.exceptions.TemplateSyntaxError: expected token 'end of statement block', got 'mhz_incapable', thus we simply rename the key before rendering the template. --- src/conf_mode/interfaces-wireless.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 9861f72db..c6c843e7b 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -33,6 +33,7 @@ from vyos.configverify import verify_vrf from vyos.ifconfig import WiFiIf from vyos.template import render from vyos.util import call +from vyos.util import vyos_dict_search from vyos import ConfigError from vyos import airbag airbag.enable() @@ -213,6 +214,11 @@ def generate(wifi): mac.dialect = mac_unix_expanded wifi['mac'] = str(mac) + # XXX: Jinja2 can not operate on a dictionary key when it starts of with a number + if '40mhz_incapable' in (vyos_dict_search('capabilities.ht', wifi) or []): + wifi['capabilities']['ht']['fourtymhz_incapable'] = wifi['capabilities']['ht']['40mhz_incapable'] + del wifi['capabilities']['ht']['40mhz_incapable'] + # render appropriate new config files depending on access-point or station mode if wifi['type'] == 'access-point': render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl', wifi, trim_blocks=True) -- cgit v1.2.3 From e0797331774a02ca23e8363fbcfe5a49fb3ca2bd Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 19 Sep 2020 22:32:18 +0200 Subject: dns: forwarding: T2900: restore proper Config() level in verify() Despite the fact that running verify on Config() is "bad" and "not as intended" the level of the configuration must match the keys that are checked by exits(). Re-set proper Config() level before querying the system nodes. --- src/conf_mode/dns_forwarding.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 51631dc16..53bc37882 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -127,6 +127,7 @@ def verify(conf, dns): f'Error: No server configured for domain {domain}')) no_system_nameservers = False + conf.set_level([]) if dns['system'] and not ( conf.exists(['system', 'name-server']) or conf.exists(['system', 'name-servers-dhcp']) ): -- cgit v1.2.3