diff options
40 files changed, 293 insertions, 189 deletions
diff --git a/data/templates/igmp-proxy/igmpproxy.conf.tmpl b/data/templates/igmp-proxy/igmpproxy.conf.tmpl index c7fc5cef5..e3966def3 100644 --- a/data/templates/igmp-proxy/igmpproxy.conf.tmpl +++ b/data/templates/igmp-proxy/igmpproxy.conf.tmpl @@ -2,36 +2,39 @@ # # autogenerated by igmp_proxy.py # -# The configuration file must define one upstream -# interface, and one or more downstream interfaces. +# The configuration file must define one upstream interface, and one or more +# downstream interfaces. # -# If multicast traffic originates outside the -# upstream subnet, the "altnet" option can be -# used in order to define legal multicast sources. -# (Se example...) +# If multicast traffic originates outside the upstream subnet, the "altnet" +# option can be used in order to define legal multicast sources. # -# The "quickleave" should be used to avoid saturation -# of the upstream link. The option should only -# be used if it's absolutely nessecary to -# accurately imitate just one Client. +# The "quickleave" should be used to avoid saturation of the upstream link. The +# option should only be used if it's absolutely nessecary to accurately imitate +# just one Client. # ######################################################## -{% if not disable_quickleave -%} +{% if disable_quickleave is not defined %} quickleave -{% endif -%} +{% endif %} +{% if interface is defined and interface is not none %} +{% for iface, config in interface.items() %} -{% for interface in interfaces %} -# Configuration for {{ interface.name }} ({{ interface.role }} interface) -{% if interface.role == 'disabled' -%} -phyint {{ interface.name }} disabled -{%- else -%} -phyint {{ interface.name }} {{ interface.role }} ratelimit 0 threshold {{ interface.threshold }} -{%- endif -%} -{%- for subnet in interface.alt_subnet %} +# Configuration for {{ iface }} ({{ config.role }} interface) +{% if config.role == 'disabled' %} +phyint {{ iface }} disabled +{% else %} +phyint {{ iface }} {{ config.role }} ratelimit 0 threshold {{ config.threshold }} +{% endif %} +{% if config.alt_subnet is defined and config.alt_subnet is not none %} +{% for subnet in config.alt_subnet %} altnet {{ subnet }} -{%- endfor %} -{%- for subnet in interface.whitelist %} +{% endfor %} +{% endif %} +{% if config.whitelist is defined and config.whitelist is not none %} +{% for subnet in config.whitelist %} whitelist {{ subnet }} -{%- endfor %} -{% endfor %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} diff --git a/interface-definitions/igmp-proxy.xml.in b/interface-definitions/igmp-proxy.xml.in index 74fec6b48..b9c52794f 100644 --- a/interface-definitions/igmp-proxy.xml.in +++ b/interface-definitions/igmp-proxy.xml.in @@ -44,7 +44,7 @@ </leafNode> <leafNode name="role"> <properties> - <help>Role of this IGMP interface</help> + <help>IGMP interface role (default: downstream)</help> <completionHelp> <list>upstream downstream disabled</list> </completionHelp> @@ -61,13 +61,14 @@ <description>Disabled interface</description> </valueHelp> <constraint> - <regex>(upstream|downstream|disabled)</regex> + <regex>^(upstream|downstream|disabled)$</regex> </constraint> </properties> + <defaultValue>downstream</defaultValue> </leafNode> <leafNode name="threshold"> <properties> - <help>TTL threshold</help> + <help>TTL threshold (default: 1)</help> <valueHelp> <format>1-255</format> <description>TTL threshold for the interfaces (default: 1)</description> @@ -75,8 +76,9 @@ <constraint> <validator name="numeric" argument="--range 1-255"/> </constraint> - <constraintErrorMessage>threshold must be between 1 and 255</constraintErrorMessage> + <constraintErrorMessage>Threshold must be between 1 and 255</constraintErrorMessage> </properties> + <defaultValue>1</defaultValue> </leafNode> <leafNode name="whitelist"> <properties> diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index b14f96364..cdcd3f9ea 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -439,13 +439,13 @@ def get_accel_dict(config, base, chap_secrets): # options which we need to update into the dictionary retrived. default_values = defaults(base) - # defaults include RADIUS server specifics per TAG node which need to be - # added to individual RADIUS servers instead - so we can simply delete them + # T2665: defaults include RADIUS server specifics per TAG node which need to + # be added to individual RADIUS servers instead - so we can simply delete them if dict_search('authentication.radius.server', default_values): del default_values['authentication']['radius']['server'] - # defaults include static-ip address per TAG node which need to be added to - # individual local users instead - so we can simply delete them + # T2665: defaults include static-ip address per TAG node which need to be + # added to individual local users instead - so we can simply delete them if dict_search('authentication.local_users.username', default_values): del default_values['authentication']['local_users']['username'] @@ -471,6 +471,7 @@ def get_accel_dict(config, base, chap_secrets): # Add individual RADIUS server default values if dict_search('authentication.radius.server', dict): + # T2665 default_values = defaults(base + ['authentication', 'radius', 'server']) for server in dict_search('authentication.radius.server', dict): @@ -484,6 +485,7 @@ def get_accel_dict(config, base, chap_secrets): # Add individual local-user default values if dict_search('authentication.local_users.username', dict): + # T2665 default_values = defaults(base + ['authentication', 'local-users', 'username']) for username in dict_search('authentication.local_users.username', dict): diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 39b80ce08..43cd7220a 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -890,9 +890,9 @@ class Interface(Control): self._config = dict_merge(tmp, self._config) render(options_file, 'dhcp-client/daemon-options.tmpl', - self._config, trim_blocks=True) + self._config) render(config_file, 'dhcp-client/ipv4.tmpl', - self._config, trim_blocks=True) + self._config) # 'up' check is mandatory b/c even if the interface is A/D, as soon as # the DHCP client is started the interface will be placed in u/u state. @@ -919,7 +919,7 @@ class Interface(Control): if enable and 'disable' not in self._config: render(config_file, 'dhcp-client/ipv6.tmpl', - self._config, trim_blocks=True) + self._config) # We must ignore any return codes. This is required to enable DHCPv6-PD # for interfaces which are yet not up and running. diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py index dd4225eb3..00dc36420 100644 --- a/python/vyos/ifconfig/tunnel.py +++ b/python/vyos/ifconfig/tunnel.py @@ -45,7 +45,6 @@ class _Tunnel(Interface): **{ 'section': 'tunnel', 'prefixes': ['tun',], - 'bridgeable': False, }, } diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index ac6dc2109..9ee798ee8 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -165,7 +165,7 @@ class WireGuardIf(Interface): **{ 'section': 'wireguard', 'prefixes': ['wg', ], - 'bridgeable': True, + 'bridgeable': False, } } options = Interface.options + \ diff --git a/python/vyos/template.py b/python/vyos/template.py index 7860b581f..b31f5bea2 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -18,24 +18,25 @@ import os from jinja2 import Environment from jinja2 import FileSystemLoader -from vyos.defaults import directories -from vyos.util import chmod, chown, makedir +from vyos.defaults import directories +from vyos.util import chmod +from vyos.util import chown +from vyos.util import makedir # Holds template filters registered via register_filter() _FILTERS = {} - -# reuse Environments with identical trim_blocks setting to improve performance +# reuse Environments with identical settings to improve performance @functools.lru_cache(maxsize=2) -def _get_environment(trim_blocks): +def _get_environment(): env = Environment( # Don't check if template files were modified upon re-rendering auto_reload=False, # Cache up to this number of templates for quick re-rendering cache_size=100, loader=FileSystemLoader(directories["templates"]), - trim_blocks=trim_blocks, + trim_blocks=True, ) env.filters.update(_FILTERS) return env @@ -62,12 +63,11 @@ def register_filter(name, func=None): return func -def render_to_string(template, content, trim_blocks=False, formater=None): +def render_to_string(template, content, formater=None): """Render a template from the template directory, raise on any errors. :param template: the path to the template relative to the template folder :param content: the dictionary of variables to put into rendering context - :param trim_blocks: controls the trim_blocks jinja2 feature :param formater: if given, it has to be a callable the rendered string is passed through @@ -78,7 +78,7 @@ def render_to_string(template, content, trim_blocks=False, formater=None): package is build (recovering the load time and overhead caused by having the file out of the code). """ - template = _get_environment(bool(trim_blocks)).get_template(template) + template = _get_environment().get_template(template) rendered = template.render(content) if formater is not None: rendered = formater(rendered) @@ -89,7 +89,6 @@ def render( destination, template, content, - trim_blocks=False, formater=None, permission=None, user=None, @@ -110,7 +109,7 @@ def render( # As we are opening the file with 'w', we are performing the rendering before # calling open() to not accidentally erase the file if rendering fails - rendered = render_to_string(template, content, trim_blocks, formater) + rendered = render_to_string(template, content, formater) # Write to file with open(destination, "w") as file: diff --git a/smoketest/scripts/cli/test_protocols_igmp-proxy.py b/smoketest/scripts/cli/test_protocols_igmp-proxy.py new file mode 100755 index 000000000..f78581fea --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_igmp-proxy.py @@ -0,0 +1,83 @@ +#!/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 <http://www.gnu.org/licenses/>. + +import os +import unittest + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.util import read_file +from vyos.util import process_named_running + +PROCESS_NAME = 'igmpproxy' +IGMP_PROXY_CONF = '/etc/igmpproxy.conf' +base_path = ['protocols', 'igmp-proxy'] +upstream_if = 'eth1' +downstream_if = 'eth2' + +class TestProtocolsIGMPProxy(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + self.session.set(['interfaces', 'ethernet', upstream_if, 'address', '172.16.1.1/24']) + + def tearDown(self): + self.session.delete(['interfaces', 'ethernet', upstream_if, 'address']) + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_igmpproxy(self): + threshold = '20' + altnet = '192.0.2.0/24' + whitelist = '10.0.0.0/8' + + self.session.set(base_path + ['disable-quickleave']) + self.session.set(base_path + ['interface', upstream_if, 'threshold', threshold]) + self.session.set(base_path + ['interface', upstream_if, 'alt-subnet', altnet]) + self.session.set(base_path + ['interface', upstream_if, 'whitelist', whitelist]) + + # Must define an upstream and at least 1 downstream interface! + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(base_path + ['interface', upstream_if, 'role', 'upstream']) + + # Interface does not exist + self.session.set(base_path + ['interface', 'eth20', 'role', 'upstream']) + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.delete(base_path + ['interface', 'eth20']) + + # Only 1 upstream interface allowed + self.session.set(base_path + ['interface', downstream_if, 'role', 'upstream']) + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(base_path + ['interface', downstream_if, 'role', 'downstream']) + + # commit changes + self.session.commit() + + # Check generated configuration + config = read_file(IGMP_PROXY_CONF) + self.assertIn(f'phyint {upstream_if} upstream ratelimit 0 threshold {threshold}', config) + self.assertIn(f'altnet {altnet}', config) + self.assertIn(f'whitelist {whitelist}', config) + self.assertIn(f'phyint {downstream_if} downstream ratelimit 0 threshold 1', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + +if __name__ == '__main__': + unittest.main() diff --git a/src/conf_mode/bcast_relay.py b/src/conf_mode/bcast_relay.py index 78daeb6be..d93a2a8f4 100755 --- a/src/conf_mode/bcast_relay.py +++ b/src/conf_mode/bcast_relay.py @@ -79,7 +79,7 @@ def generate(relay): config['instance'] = instance render(config_file_base + instance, 'bcast-relay/udp-broadcast-relay.tmpl', - config, trim_blocks=True) + config) return None diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index ef52cbfd3..c44e6c974 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -114,10 +114,10 @@ def generate(dns): return None render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl', - dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group) + dns, user=pdns_rec_user, group=pdns_rec_group) render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl', - dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group) + dns, user=pdns_rec_user, group=pdns_rec_group) # if vyos-hostsd didn't create its files yet, create them (empty) for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py index 93e995b78..6d39c6644 100755 --- a/src/conf_mode/dynamic_dns.py +++ b/src/conf_mode/dynamic_dns.py @@ -131,7 +131,9 @@ def generate(dyndns): if not dyndns: return None - render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns, trim_blocks=True, permission=0o600) + render(config_file, 'dynamic-dns/ddclient.conf.tmpl', dyndns, + permission=0o600) + return None def apply(dyndns): diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index de228f0f8..a6e2d9c8c 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -159,7 +159,7 @@ def generate(https): if 'server_block_list' not in https or not https['server_block_list']: https['server_block_list'] = [default_server_block] - render(config_file, 'https/nginx.default.tmpl', https, trim_blocks=True) + render(config_file, 'https/nginx.default.tmpl', https) return None diff --git a/src/conf_mode/igmp_proxy.py b/src/conf_mode/igmp_proxy.py index 754f46566..fb030c9f3 100755 --- a/src/conf_mode/igmp_proxy.py +++ b/src/conf_mode/igmp_proxy.py @@ -17,90 +17,65 @@ import os from sys import exit -from copy import deepcopy - from netifaces import interfaces + 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.util import dict_search +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() config_file = r'/etc/igmpproxy.conf' -default_config_data = { - 'disable': False, - 'disable_quickleave': False, - 'interfaces': [], -} - def get_config(config=None): - igmp_proxy = deepcopy(default_config_data) if config: conf = config else: conf = Config() - base = ['protocols', 'igmp-proxy'] - if not conf.exists(base): - return None - else: - conf.set_level(base) - - # Network interfaces to listen on - if conf.exists(['disable']): - igmp_proxy['disable'] = True - - # Option to disable "quickleave" - if conf.exists(['disable-quickleave']): - igmp_proxy['disable_quickleave'] = True - for intf in conf.list_nodes(['interface']): - conf.set_level(base + ['interface', intf]) - interface = { - 'name': intf, - 'alt_subnet': [], - 'role': 'downstream', - 'threshold': '1', - 'whitelist': [] - } - - if conf.exists(['alt-subnet']): - interface['alt_subnet'] = conf.return_values(['alt-subnet']) + base = ['protocols', 'igmp-proxy'] + igmp_proxy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - if conf.exists(['role']): - interface['role'] = conf.return_value(['role']) + if 'interface' in igmp_proxy: + # T2665: we must add the tagNode defaults individually until this is + # moved to the base class + default_values = defaults(base + ['interface']) + for interface in igmp_proxy['interface']: + igmp_proxy['interface'][interface] = dict_merge(default_values, + igmp_proxy['interface'][interface]) - if conf.exists(['threshold']): - interface['threshold'] = conf.return_value(['threshold']) - if conf.exists(['whitelist']): - interface['whitelist'] = conf.return_values(['whitelist']) + if conf.exists(['protocols', 'igmp']): + igmp_proxy.update({'igmp_configured': ''}) - # Append interface configuration to global configuration list - igmp_proxy['interfaces'].append(interface) + if conf.exists(['protocols', 'pim']): + igmp_proxy.update({'pim_configured': ''}) return igmp_proxy def verify(igmp_proxy): # bail out early - looks like removal from running config - if igmp_proxy is None: + if not igmp_proxy or 'disable' in igmp_proxy: return None - # bail out early - service is disabled - if igmp_proxy['disable']: - return None + if 'igmp_configured' in igmp_proxy or 'pim_configured' in igmp_proxy: + raise ConfigError('Can not configure both IGMP proxy and PIM '\ + 'at the same time') # at least two interfaces are required, one upstream and one downstream - if len(igmp_proxy['interfaces']) < 2: - raise ConfigError('Must define an upstream and at least 1 downstream interface!') + if 'interface' not in igmp_proxy or len(igmp_proxy['interface']) < 2: + raise ConfigError('Must define exactly one upstream and at least one ' \ + 'downstream interface!') upstream = 0 - for interface in igmp_proxy['interfaces']: - if interface['name'] not in interfaces(): - raise ConfigError('Interface "{}" does not exist'.format(interface['name'])) - if "upstream" == interface['role']: + for interface, config in igmp_proxy['interface'].items(): + if interface not in interfaces(): + raise ConfigError(f'Interface "{interface}" does not exist') + if dict_search('role', config) == 'upstream': upstream += 1 if upstream == 0: @@ -112,19 +87,20 @@ def verify(igmp_proxy): def generate(igmp_proxy): # bail out early - looks like removal from running config - if igmp_proxy is None: + if not igmp_proxy: return None # bail out early - service is disabled, but inform user - if igmp_proxy['disable']: - print('Warning: IGMP Proxy will be deactivated because it is disabled') + if 'disable' in igmp_proxy: + print('WARNING: IGMP Proxy will be deactivated because it is disabled') return None render(config_file, 'igmp-proxy/igmpproxy.conf.tmpl', igmp_proxy) + return None def apply(igmp_proxy): - if igmp_proxy is None or igmp_proxy['disable']: + if not igmp_proxy or 'disable' in igmp_proxy: # IGMP Proxy support is removed in the commit call('systemctl stop igmpproxy.service') if os.path.exists(config_file): diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 0e661c84b..25920f893 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -467,7 +467,7 @@ def generate(openvpn): # Generate User/Password authentication file if 'authentication' in openvpn: render(openvpn['auth_user_pass_file'], 'openvpn/auth.pw.tmpl', openvpn, - trim_blocks=True, user=user, group=group, permission=0o600) + user=user, group=group, permission=0o600) else: # delete old auth file if present if os.path.isfile(openvpn['auth_user_pass_file']): @@ -482,13 +482,12 @@ def generate(openvpn): client_config['server_subnet'] = dict_search('server.subnet', openvpn) render(client_file, 'openvpn/client.conf.tmpl', client_config, - trim_blocks=True, user=user, group=group) + user=user, group=group) # we need to support quoting of raw parameters from OpenVPN CLI # see https://phabricator.vyos.net/T1632 render(cfg_file.format(**openvpn), 'openvpn/server.conf.tmpl', openvpn, - trim_blocks=True, formater=lambda _: _.replace(""", '"'), - user=user, group=group) + formater=lambda _: _.replace(""", '"'), user=user, group=group) # Fixup file permissions for file in fix_permissions: diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index ee3b142c8..c31e49574 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -93,25 +93,25 @@ def generate(pppoe): return None # Create PPP configuration files - render(config_pppoe, 'pppoe/peer.tmpl', - pppoe, trim_blocks=True, permission=0o755) + render(config_pppoe, 'pppoe/peer.tmpl', pppoe, permission=0o755) + # Create script for ip-pre-up.d - render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl', - pppoe, trim_blocks=True, permission=0o755) + render(script_pppoe_pre_up, 'pppoe/ip-pre-up.script.tmpl', pppoe, + permission=0o755) # Create script for ip-up.d - render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl', - pppoe, trim_blocks=True, permission=0o755) + render(script_pppoe_ip_up, 'pppoe/ip-up.script.tmpl', pppoe, + permission=0o755) # Create script for ip-down.d - render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl', - pppoe, trim_blocks=True, permission=0o755) + render(script_pppoe_ip_down, 'pppoe/ip-down.script.tmpl', pppoe, + permission=0o755) # Create script for ipv6-up.d - render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', - pppoe, trim_blocks=True, permission=0o755) + render(script_pppoe_ipv6_up, 'pppoe/ipv6-up.script.tmpl', pppoe, + permission=0o755) if 'dhcpv6_options' in pppoe and 'pd' in pppoe['dhcpv6_options']: # ipv6.tmpl relies on ifname - this should be made consitent in the # future better then double key-ing the same value - render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe, trim_blocks=True) + render(config_wide_dhcp6c, 'dhcp-client/ipv6.tmpl', pppoe) return None diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index d302c7df7..b25fcd4e0 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -241,10 +241,12 @@ def generate(wifi): # 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) + render(hostapd_conf.format(**wifi), 'wifi/hostapd.conf.tmpl', + wifi) elif wifi['type'] == 'station': - render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl', wifi, trim_blocks=True) + render(wpa_suppl_conf.format(**wifi), 'wifi/wpa_supplicant.conf.tmpl', + wifi) return None diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index bce3405d0..976953b31 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -91,21 +91,21 @@ def generate(wwan): wwan['device'] = find_device_file(wwan['device']) # Create PPP configuration files - render(config_wwan, 'wwan/peer.tmpl', wwan, trim_blocks=True) + render(config_wwan, 'wwan/peer.tmpl', wwan) # Create PPP chat script - render(config_wwan_chat, 'wwan/chat.tmpl', wwan, trim_blocks=True) + render(config_wwan_chat, 'wwan/chat.tmpl', wwan) # generated script file must be executable # Create script for ip-pre-up.d render(script_wwan_pre_up, 'wwan/ip-pre-up.script.tmpl', - wwan, trim_blocks=True, permission=0o755) + wwan, permission=0o755) # Create script for ip-up.d render(script_wwan_ip_up, 'wwan/ip-up.script.tmpl', - wwan, trim_blocks=True, permission=0o755) + wwan, permission=0o755) # Create script for ip-down.d render(script_wwan_ip_down, 'wwan/ip-down.script.tmpl', - wwan, trim_blocks=True, permission=0o755) + wwan, permission=0o755) return None diff --git a/src/conf_mode/ipsec-settings.py b/src/conf_mode/ipsec-settings.py index 11a5b7aaa..a65e8b567 100755 --- a/src/conf_mode/ipsec-settings.py +++ b/src/conf_mode/ipsec-settings.py @@ -170,12 +170,12 @@ def verify(data): raise ConfigError("L2TP VPN configuration error: \"vpn ipsec ipsec-interfaces\" must be specified.") def generate(data): - render(charon_conf_file, 'ipsec/charon.tmpl', data, trim_blocks=True) + render(charon_conf_file, 'ipsec/charon.tmpl', data) if data["ipsec_l2tp"]: remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_secrets_file) # old_umask = os.umask(0o077) - # render(ipsec_secrets_file, 'ipsec/ipsec.secrets.tmpl', data, trim_blocks=True) + # render(ipsec_secrets_file, 'ipsec/ipsec.secrets.tmpl', data) # os.umask(old_umask) ## Use this method while IPSec CLI handler won't be overwritten to python write_ipsec_secrets(data) @@ -186,12 +186,12 @@ def generate(data): if not os.path.exists(ipsec_ra_conn_dir): os.makedirs(ipsec_ra_conn_dir) - render(ipsec_ra_conn_file, 'ipsec/remote-access.tmpl', data, trim_blocks=True) + render(ipsec_ra_conn_file, 'ipsec/remote-access.tmpl', data) os.umask(old_umask) remove_confs(delim_ipsec_l2tp_begin, delim_ipsec_l2tp_end, ipsec_conf_file) # old_umask = os.umask(0o077) - # render(ipsec_conf_file, 'ipsec/ipsec.conf.tmpl', data, trim_blocks=True) + # render(ipsec_conf_file, 'ipsec/ipsec.conf.tmpl', data) # os.umask(old_umask) ## Use this method while IPSec CLI handler won't be overwritten to python write_ipsec_conf(data) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index b66cd370a..b467f3d74 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -268,7 +268,8 @@ def verify(nat): return None def generate(nat): - render(iptables_nat_config, 'firewall/nftables-nat.tmpl', nat, trim_blocks=True, permission=0o755) + render(iptables_nat_config, 'firewall/nftables-nat.tmpl', nat, + permission=0o755) return None def apply(nat): diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index d6453ec83..b102b3e9e 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -53,8 +53,8 @@ def generate(ntp): if not ntp: return None - render(config_file, 'ntp/ntp.conf.tmpl', ntp, trim_blocks=True) - render(systemd_override, 'ntp/override.conf.tmpl', ntp, trim_blocks=True) + render(config_file, 'ntp/ntp.conf.tmpl', ntp) + render(systemd_override, 'ntp/override.conf.tmpl', ntp) return None diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 981ff9fe9..642738b09 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -80,10 +80,8 @@ def generate(bgp): bgp[asn]['asn'] = asn # render(config) not needed, its only for debug - render(config_file, 'frr/bgp.frr.tmpl', bgp[asn], trim_blocks=True) - - bgp['new_frr_config'] = render_to_string('frr/bgp.frr.tmpl', bgp[asn], - trim_blocks=True) + render(config_file, 'frr/bgp.frr.tmpl', bgp[asn]) + bgp['new_frr_config'] = render_to_string('frr/bgp.frr.tmpl', bgp[asn]) return None diff --git a/src/conf_mode/protocols_igmp.py b/src/conf_mode/protocols_igmp.py index 6f4fc784d..8606e7364 100755 --- a/src/conf_mode/protocols_igmp.py +++ b/src/conf_mode/protocols_igmp.py @@ -21,8 +21,9 @@ from sys import exit from vyos import ConfigError from vyos.config import Config -from vyos.util import call +from vyos.util import call, process_named_running from vyos.template import render +from signal import SIGTERM from vyos import airbag airbag.enable() @@ -36,12 +37,20 @@ def get_config(config=None): conf = Config() igmp_conf = { 'igmp_conf' : False, + 'pim_conf' : False, + 'igmp_proxy_conf' : False, 'old_ifaces' : {}, 'ifaces' : {} } if not (conf.exists('protocols igmp') or conf.exists_effective('protocols igmp')): return None + if conf.exists('protocols igmp-proxy'): + igmp_conf['igmp_proxy_conf'] = True + + if conf.exists('protocols pim'): + igmp_conf['pim_conf'] = True + if conf.exists('protocols igmp'): igmp_conf['igmp_conf'] = True @@ -79,6 +88,10 @@ def verify(igmp): return None if igmp['igmp_conf']: + # Check conflict with IGMP-Proxy + if igmp['igmp_proxy_conf']: + raise ConfigError(f"IGMP proxy and PIM cannot be both configured at the same time") + # Check interfaces if not igmp['ifaces']: raise ConfigError(f"IGMP require defined interfaces!") @@ -99,9 +112,16 @@ def apply(igmp): if igmp is None: return None - if os.path.exists(config_file): - call(f'vtysh -d pimd -f {config_file}') - os.remove(config_file) + pim_pid = process_named_running('pimd') + if igmp['igmp_conf'] or igmp['pim_conf']: + if not pim_pid: + call(f'pimd -d -F traditional --daemon -A 127.0.0.1') + + if os.path.exists(config_file): + call(f'vtysh -d pimd -f {config_file}') + os.remove(config_file) + elif pim_pid: + os.kill(int(pim_pid), SIGTERM) return None diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py index 03e11c6c4..df03fd990 100755 --- a/src/conf_mode/protocols_isis.py +++ b/src/conf_mode/protocols_isis.py @@ -107,10 +107,10 @@ def generate(isis): isis[process]['process'] = process # render(config) not needed, its only for debug - render(config_file, 'frr/isis.frr.tmpl', isis[process], trim_blocks=True) + render(config_file, 'frr/isis.frr.tmpl', isis[process]) isis['new_frr_config'] = render_to_string('frr/isis.frr.tmpl', - isis[process], trim_blocks=True) + isis[process]) return None diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index da298325c..791b18110 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -68,8 +68,7 @@ def generate(mpls): mpls['new_frr_config'] = '' return None - mpls['new_frr_config'] = render_to_string('frr/ldpd.frr.tmpl', mpls, - trim_blocks=True) + mpls['new_frr_config'] = render_to_string('frr/ldpd.frr.tmpl', mpls) return None def apply(mpls): diff --git a/src/conf_mode/protocols_pim.py b/src/conf_mode/protocols_pim.py index 6d333e19a..8a9f034d5 100755 --- a/src/conf_mode/protocols_pim.py +++ b/src/conf_mode/protocols_pim.py @@ -21,8 +21,9 @@ from sys import exit from vyos.config import Config from vyos import ConfigError -from vyos.util import call +from vyos.util import call, process_named_running from vyos.template import render +from signal import SIGTERM from vyos import airbag airbag.enable() @@ -36,6 +37,8 @@ def get_config(config=None): conf = Config() pim_conf = { 'pim_conf' : False, + 'igmp_conf' : False, + 'igmp_proxy_conf' : False, 'old_pim' : { 'ifaces' : {}, 'rp' : {} @@ -48,6 +51,12 @@ def get_config(config=None): if not (conf.exists('protocols pim') or conf.exists_effective('protocols pim')): return None + if conf.exists('protocols igmp-proxy'): + pim_conf['igmp_proxy_conf'] = True + + if conf.exists('protocols igmp'): + pim_conf['igmp_conf'] = True + if conf.exists('protocols pim'): pim_conf['pim_conf'] = True @@ -92,6 +101,10 @@ def verify(pim): return None if pim['pim_conf']: + # Check conflict with IGMP-Proxy + if pim['igmp_proxy_conf']: + raise ConfigError(f"IGMP proxy and PIM cannot be both configured at the same time") + # Check interfaces if not pim['pim']['ifaces']: raise ConfigError(f"PIM require defined interfaces!") @@ -126,9 +139,16 @@ def apply(pim): if pim is None: return None - if os.path.exists(config_file): - call("vtysh -d pimd -f " + config_file) - os.remove(config_file) + pim_pid = process_named_running('pimd') + if pim['igmp_conf'] or pim['pim_conf']: + if not pim_pid: + call(f'pimd -d -F traditional --daemon -A 127.0.0.1') + + if os.path.exists(config_file): + call("vtysh -d pimd -f " + config_file) + os.remove(config_file) + elif pim_pid: + os.kill(int(pim_pid), SIGTERM) return None diff --git a/src/conf_mode/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py index 27d0ee60c..67edeb630 100755 --- a/src/conf_mode/service_ids_fastnetmon.py +++ b/src/conf_mode/service_ids_fastnetmon.py @@ -56,7 +56,7 @@ def verify(fastnetmon): if not os.access(fastnetmon["alert_script"], os.X_OK): raise ConfigError('Script {0} does not have permissions for execution'.format(fastnetmon["alert_script"])) else: - raise ConfigError('File {0} does not exists!'.format(fastnetmon["alert_script"])) + raise ConfigError('File {0} does not exists!'.format(fastnetmon["alert_script"])) def generate(fastnetmon): if not fastnetmon: @@ -67,8 +67,8 @@ def generate(fastnetmon): return - render(config_file, 'ids/fastnetmon.tmpl', fastnetmon, trim_blocks=True) - render(networks_list, 'ids/fastnetmon_networks_list.tmpl', fastnetmon, trim_blocks=True) + render(config_file, 'ids/fastnetmon.tmpl', fastnetmon) + render(networks_list, 'ids/fastnetmon_networks_list.tmpl', fastnetmon) return None diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py index 68c554360..f676fdbbe 100755 --- a/src/conf_mode/service_ipoe-server.py +++ b/src/conf_mode/service_ipoe-server.py @@ -283,7 +283,7 @@ def generate(ipoe): if not ipoe: return None - render(ipoe_conf, 'accel-ppp/ipoe.config.tmpl', ipoe, trim_blocks=True) + render(ipoe_conf, 'accel-ppp/ipoe.config.tmpl', ipoe) if ipoe['auth_mode'] == 'local': render(ipoe_chap_secrets, 'accel-ppp/chap-secrets.ipoe.tmpl', ipoe) diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 2260b3fe1..9fbd531da 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -73,11 +73,11 @@ def generate(pppoe): if not pppoe: return None - render(pppoe_conf, 'accel-ppp/pppoe.config.tmpl', pppoe, trim_blocks=True) + render(pppoe_conf, 'accel-ppp/pppoe.config.tmpl', pppoe) if dict_search('authentication.mode', pppoe) == 'local': render(pppoe_chap_secrets, 'accel-ppp/chap-secrets.config_dict.tmpl', - pppoe, trim_blocks=True, permission=0o640) + pppoe, permission=0o640) else: if os.path.exists(pppoe_chap_secrets): os.unlink(pppoe_chap_secrets) diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index 687d7068f..65eb11ce3 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -93,7 +93,7 @@ def generate(rtradv): if not rtradv: return None - render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, trim_blocks=True, permission=0o644) + render(config_file, 'router-advert/radvd.conf.tmpl', rtradv, permission=0o644) return None def apply(rtradv): diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index e07745963..8f99053d2 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -66,8 +66,8 @@ def generate(ssh): return None - render(config_file, 'ssh/sshd_config.tmpl', ssh, trim_blocks=True) - render(systemd_override, 'ssh/override.conf.tmpl', ssh, trim_blocks=True) + render(config_file, 'ssh/sshd_config.tmpl', ssh) + render(systemd_override, 'ssh/override.conf.tmpl', ssh) return None diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 2c0bbd4f7..39bad717d 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -256,7 +256,7 @@ def generate(login): if len(login['radius_server']) > 0: render(radius_config_file, 'system-login/pam_radius_auth.conf.tmpl', - login, trim_blocks=True) + login) uid = getpwnam('root').pw_uid gid = getpwnam('root').pw_gid diff --git a/src/conf_mode/system-option.py b/src/conf_mode/system-option.py index 2376e5d44..447c97a78 100755 --- a/src/conf_mode/system-option.py +++ b/src/conf_mode/system-option.py @@ -73,8 +73,8 @@ def verify(options): return None def generate(options): - render(curlrc_config, 'system/curlrc.tmpl', options, trim_blocks=True) - render(ssh_config, 'system/ssh_config.tmpl', options, trim_blocks=True) + render(curlrc_config, 'system/curlrc.tmpl', options) + render(ssh_config, 'system/ssh_config.tmpl', options) return None def apply(options): diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py index b1daf7a82..3d8a51cd8 100755 --- a/src/conf_mode/system-syslog.py +++ b/src/conf_mode/system-syslog.py @@ -203,12 +203,12 @@ def generate(c): return None conf = '/etc/rsyslog.d/vyos-rsyslog.conf' - render(conf, 'syslog/rsyslog.conf.tmpl', c, trim_blocks=True) + render(conf, 'syslog/rsyslog.conf.tmpl', c) # eventually write for each file its own logrotate file, since size is # defined it shouldn't matter conf = '/etc/logrotate.d/vyos-rsyslog' - render(conf, 'syslog/logrotate.tmpl', c, trim_blocks=True) + render(conf, 'syslog/logrotate.tmpl', c) def verify(c): diff --git a/src/conf_mode/system_lcd.py b/src/conf_mode/system_lcd.py index a540d1b9e..b5ce32beb 100755 --- a/src/conf_mode/system_lcd.py +++ b/src/conf_mode/system_lcd.py @@ -61,9 +61,9 @@ def generate(lcd): lcd['device'] = find_device_file(lcd['device']) # Render config file for daemon LCDd - render(lcdd_conf, 'lcd/LCDd.conf.tmpl', lcd, trim_blocks=True) + render(lcdd_conf, 'lcd/LCDd.conf.tmpl', lcd) # Render config file for client lcdproc - render(lcdproc_conf, 'lcd/lcdproc.conf.tmpl', lcd, trim_blocks=True) + render(lcdproc_conf, 'lcd/lcdproc.conf.tmpl', lcd) return None diff --git a/src/conf_mode/tftp_server.py b/src/conf_mode/tftp_server.py index 56e195b6a..2409eec1f 100755 --- a/src/conf_mode/tftp_server.py +++ b/src/conf_mode/tftp_server.py @@ -92,7 +92,7 @@ def generate(tftpd): config['listen_address'] = f'[{address}]:{port} -6' file = config_file + str(idx) - render(file, 'tftp-server/default.tmpl', config, trim_blocks=True) + render(file, 'tftp-server/default.tmpl', config) idx = idx + 1 return None diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index 80eb8daf2..e970d2ef5 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -357,7 +357,7 @@ def generate(l2tp): if not l2tp: return None - render(l2tp_conf, 'accel-ppp/l2tp.config.tmpl', l2tp, trim_blocks=True) + render(l2tp_conf, 'accel-ppp/l2tp.config.tmpl', l2tp) if l2tp['auth_mode'] == 'local': render(l2tp_chap_secrets, 'accel-ppp/chap-secrets.tmpl', l2tp) diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py index af8604972..b2aa13c0d 100755 --- a/src/conf_mode/vpn_openconnect.py +++ b/src/conf_mode/vpn_openconnect.py @@ -34,12 +34,10 @@ 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'] @@ -47,10 +45,12 @@ def get_config(): return None ocserv = 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) ocserv = dict_merge(default_values, ocserv) - return ocserv + return ocserv def verify(ocserv): if ocserv is None: @@ -88,7 +88,7 @@ def verify(ocserv): 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" + ocserv["network_settings"]["push_route"] = "default" else: raise ConfigError('openconnect network settings required') @@ -99,19 +99,18 @@ def generate(ocserv): 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_cfg, 'ocserv/radius_conf.tmpl', ocserv["authentication"]["radius"]) # Render radius servers - render(radius_servers, 'ocserv/radius_servers.tmpl', ocserv["authentication"]["radius"], trim_blocks=True) + render(radius_servers, 'ocserv/radius_servers.tmpl', ocserv["authentication"]["radius"]) 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(ocserv_passwd, 'ocserv/ocserv_passwd.tmpl', ocserv["authentication"]["local_users"]) # Render config - render(ocserv_conf, 'ocserv/ocserv_config.tmpl', ocserv, trim_blocks=True) - + render(ocserv_conf, 'ocserv/ocserv_config.tmpl', ocserv) def apply(ocserv): diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py index 3125ee9d0..30abe4782 100755 --- a/src/conf_mode/vpn_pptp.py +++ b/src/conf_mode/vpn_pptp.py @@ -264,10 +264,10 @@ def generate(pptp): if not pptp: return None - render(pptp_conf, 'accel-ppp/pptp.config.tmpl', pptp, trim_blocks=True) + render(pptp_conf, 'accel-ppp/pptp.config.tmpl', pptp) if pptp['local_users']: - render(pptp_chap_secrets, 'accel-ppp/chap-secrets.tmpl', pptp, trim_blocks=True) + render(pptp_chap_secrets, 'accel-ppp/chap-secrets.tmpl', pptp) os.chmod(pptp_chap_secrets, S_IRUSR | S_IWUSR | S_IRGRP) else: if os.path.exists(pptp_chap_secrets): diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py index 1b2b80ce5..47367f125 100755 --- a/src/conf_mode/vpn_sstp.py +++ b/src/conf_mode/vpn_sstp.py @@ -82,11 +82,11 @@ def generate(sstp): return None # accel-cmd reload doesn't work so any change results in a restart of the daemon - render(sstp_conf, 'accel-ppp/sstp.config.tmpl', sstp, trim_blocks=True) + render(sstp_conf, 'accel-ppp/sstp.config.tmpl', sstp) if dict_search('authentication.mode', sstp) == 'local': render(sstp_chap_secrets, 'accel-ppp/chap-secrets.config_dict.tmpl', - sstp, trim_blocks=True, permission=0o640) + sstp, permission=0o640) else: if os.path.exists(sstp_chap_secrets): os.unlink(sstp_chap_secrets) diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py index 172ce71b7..fa19e7d45 100755 --- a/src/op_mode/lldp_op.py +++ b/src/op_mode/lldp_op.py @@ -117,7 +117,7 @@ if __name__ == '__main__': parser.print_help() exit(1) - tmpl = jinja2.Template(lldp_out, trim_blocks=True) + tmpl = jinja2.Template(lldp_out) config_text = tmpl.render(parse_data(neighbors)) print(config_text) |