From d654990111894c0f89ca4141862775ddd7a22b76 Mon Sep 17 00:00:00 2001 From: hagbard Date: Sat, 27 Apr 2019 18:40:32 -0700 Subject: [rsyslog] T1358 - typo fixed os.path.exists --- src/conf_mode/syslog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/syslog.py b/src/conf_mode/syslog.py index c8541a4a0..105f35657 100755 --- a/src/conf_mode/syslog.py +++ b/src/conf_mode/syslog.py @@ -279,7 +279,7 @@ def verify(c): raise ConfigError('Invalid logging level ' + s + ' set in '+ conf + ' ' + item) def apply(c): - if not os.path.exits('/var/run/rsyslogd.pid'): + if not os.path.exists('/var/run/rsyslogd.pid'): os.system("sudo systemctl start rsyslog >/dev/null") else: os.system("sudo systemctl restart rsyslog >/dev/null") -- cgit v1.2.3 From 09df82d3a4ff2e0912992cb5c73d5d9fce7070dd Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 5 May 2019 14:55:35 +0200 Subject: [dhcp-server] T103: wrong hostnames in hosts file --- src/conf_mode/dhcp_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index c8ae2fa91..78927a847 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -186,7 +186,7 @@ shared-network {{ network.name }} { {%- endif -%} {%- for host in subnet.static_mapping %} {% if not host.disabled -%} - host {{ network.name }}_{{ host.name }} { + host {% if host_decl_name -%} {{ host.name }} {%- else -%} {{ network.name }}_{{ host.name }} {%- endif %} { fixed-address {{ host.ip_address }}; hardware ethernet {{ host.mac_address }}; {%- if host.static_parameters %} -- cgit v1.2.3 From 95b4b1e2ec1fc518e18406da638bdc0e025310d8 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 8 May 2019 00:32:52 +0200 Subject: [VRRP] T1371: add quotes around the health check script string. --- src/conf_mode/vrrp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index bc833b63f..85c6ad580 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -39,7 +39,7 @@ config_tmpl = """ {% if group.health_check_script -%} vrrp_script healthcheck_{{ group.name }} { - script {{ group.health_check_script }} + script "{{ group.health_check_script }}" interval {{ group.health_check_interval }} fall {{ group.health_check_count }} rise 1 -- cgit v1.2.3 From ae1c37284c9a91e1be934593812d3bd62e706d3b Mon Sep 17 00:00:00 2001 From: hagbard Date: Tue, 21 May 2019 14:39:08 -0700 Subject: [pppoe-server] T1393 - pppoe IPv6 pool doesn't work --- src/conf_mode/accel_pppoe.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/accel_pppoe.py b/src/conf_mode/accel_pppoe.py index 3b3bf8cac..7a9878a1d 100755 --- a/src/conf_mode/accel_pppoe.py +++ b/src/conf_mode/accel_pppoe.py @@ -47,6 +47,8 @@ pppoe ippool {% if client_ipv6_pool %} ipv6pool +ipv6_nd +ipv6_dhcp {% endif %} chap-secrets auth_pap @@ -85,7 +87,7 @@ disable gw-ip-address={{ppp_gw}} {% if client_ip_pool %} {{client_ip_pool}} -{% endif -%} +{% endif %} {% if client_ip_subnets %} {% for sn in client_ip_subnets %} @@ -101,7 +103,7 @@ gw-ip-address={{ppp_gw}} {% for prfx in client_ipv6_pool['delegate-prefix']: %} delegate={{prfx}} {% endfor %} -{% endif -%} +{% endif %} {% if dns %} [dns] @@ -111,14 +113,14 @@ dns1={{dns[0]}} {% if dns[1] %} dns2={{dns[1]}} {% endif -%} -{% endif -%} +{% endif %} {% if dnsv6 %} -[dnsv6] +[ipv6-dns] {% for srv in dnsv6: %} -dns={{srv}} +{{srv}} {% endfor %} -{% endif -%} +{% endif %} {% if wins %} [wins] @@ -208,6 +210,10 @@ lcp-echo-failure=3 {% if ppp_options['ipv4'] %} ipv4={{ppp_options['ipv4']}} {% endif %} +{% if client_ipv6_pool %} +ipv6=allow +{% endif %} + {% if ppp_options['ipv6'] %} ipv6={{ppp_options['ipv6']}} {% if ppp_options['ipv6-intf-id'] %} @@ -220,6 +226,7 @@ ipv6-peer-intf-id={{ppp_options['ipv6-peer-intf-id']}} ipv6-accept-peer-intf-id={{ppp_options['ipv6-accept-peer-intf-id']}} {% endif %} {% endif %} + mtu={{mtu}} [pppoe] -- cgit v1.2.3 From 0cdf63668d5df74d58d8eb5a155cdf2d4693c9cf Mon Sep 17 00:00:00 2001 From: Kim Hagen Date: Tue, 4 Jun 2019 15:22:39 +0200 Subject: T1379: Deprecated functions in /sbin/dhclient-script --- src/conf_mode/dns_forwarding.py | 64 +++++--- src/conf_mode/host_name.py | 321 +++++++++++++++++++++------------------- 2 files changed, 210 insertions(+), 175 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 135f6fec0..7559a0af6 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -19,12 +19,18 @@ import sys import os -import netifaces +import argparse import jinja2 +import netifaces from vyos.config import Config from vyos import ConfigError + +parser = argparse.ArgumentParser() +parser.add_argument("--dhclient", action="store_true", + help="Started from dhclient-script") + config_file = r'/etc/powerdns/recursor.conf' # XXX: pdns recursor doesn't like whitespace near entry separators, @@ -84,31 +90,36 @@ default_config_data = { 'name_servers': [], 'negative_ttl': 3600, 'domains': [], - 'dnssec' : 'process-no-validate' + 'dnssec': 'process-no-validate' } # borrowed from: https://github.com/donjajo/py-world/blob/master/resolvconfReader.py, THX! def get_resolvers(file): - resolvers = [] try: with open(file, 'r') as resolvconf: - for line in resolvconf.readlines(): - line = line.split('#',1)[0]; - line = line.rstrip(); - if 'nameserver' in line: - resolvers.append(line.split()[1]) + lines = [line.split('#', 1)[0].rstrip() + for line in resolvconf.readlines()] + resolvers = [line.split()[1] + for line in lines if 'nameserver' in line] return resolvers except IOError: return [] -def get_config(): + +def get_config(arguments): dns = default_config_data conf = Config() + + if arguments.dhclient: + conf.exists = conf.exists_effective + conf.return_value = conf.return_effective_value + conf.return_values = conf.return_effective_values + if not conf.exists('service dns forwarding'): return None - else: - conf.set_level('service dns forwarding') + + conf.set_level('service dns forwarding') if conf.exists('cache-size'): cache_size = conf.return_value('cache-size') @@ -139,7 +150,8 @@ def get_config(): system_name_servers = [] system_name_servers = conf.return_values('name-server') if not system_name_servers: - print("DNS forwarding warning: No name-servers set under 'system name-server'\n") + print( + "DNS forwarding warning: No name-servers set under 'system name-server'\n") else: dns['name_servers'] = dns['name_servers'] + system_name_servers conf.set_level('service dns forwarding') @@ -171,9 +183,10 @@ def get_config(): try: addrs = netifaces.ifaddresses(interface) except ValueError: - print("WARNING: interface {0} does not exist".format(interface)) + print( + "WARNING: interface {0} does not exist".format(interface)) continue - + if netifaces.AF_INET in addrs.keys(): for ip4 in addrs[netifaces.AF_INET]: listen4.append(ip4['addr']) @@ -183,7 +196,8 @@ def get_config(): listen6.append(ip6['addr']) if (not listen4) and (not (listen6)): - print("WARNING: interface {0} has no configured addresses".format(interface)) + print( + "WARNING: interface {0} has no configured addresses".format(interface)) dns['listen_on'] = dns['listen_on'] + listen4 + listen6 @@ -195,31 +209,37 @@ def get_config(): interfaces = [] interfaces = conf.return_values('dhcp') for interface in interfaces: - dhcp_resolvers = get_resolvers("/etc/resolv.conf.dhclient-new-{0}".format(interface)) + dhcp_resolvers = get_resolvers( + "/etc/resolv.conf.dhclient-new-{0}".format(interface)) if dhcp_resolvers: dns['name_servers'] = dns['name_servers'] + dhcp_resolvers return dns + def bracketize_ipv6_addrs(addrs): """Wraps each IPv6 addr in addrs in [], leaving IPv4 addrs untouched.""" return ['[{0}]'.format(a) if a.count(':') > 1 else a for a in addrs] + def verify(dns): # bail out early - looks like removal from running config if dns is None: return None if not dns['listen_on']: - raise ConfigError("Error: DNS forwarding requires either a listen-address (preferred) or a listen-on option") + raise ConfigError( + "Error: DNS forwarding requires either a listen-address (preferred) or a listen-on option") if dns['domains']: for domain in dns['domains']: if not domain['servers']: - raise ConfigError('Error: No server configured for domain {0}'.format(domain['name'])) + raise ConfigError( + 'Error: No server configured for domain {0}'.format(domain['name'])) return None + def generate(dns): # bail out early - looks like removal from running config if dns is None: @@ -232,19 +252,21 @@ def generate(dns): f.write(config_text) return None + def apply(dns): if dns is not None: os.system("systemctl restart pdns-recursor") else: # DNS forwarding is removed in the commit os.system("systemctl stop pdns-recursor") - os.unlink(config_file) + if os.path.isfile(config_file): + os.unlink(config_file) - return None if __name__ == '__main__': + args = parser.parse_args() try: - c = get_config() + c = get_config(args) verify(c) generate(c) apply(c) diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 81a52e87f..621ccd7e0 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -23,14 +23,19 @@ conf-mode script for 'system host-name' and 'system domain-name'. import os import re import sys -import subprocess import copy -import jinja2 import glob +import argparse +import jinja2 from vyos.config import Config from vyos import ConfigError + +parser = argparse.ArgumentParser() +parser.add_argument("--dhclient", action="store_true", + help="Started from dhclient-script") + config_file_hosts = '/etc/hosts' config_file_resolv = '/etc/resolv.conf' @@ -72,32 +77,6 @@ search {{ domain_search | join(" ") }} """ -# borrowed from: https://github.com/donjajo/py-world/blob/master/resolvconfReader.py, THX! -def get_resolvers(file): - resolvers = [] - try: - with open(file, 'r') as resolvconf: - for line in resolvconf.readlines(): - line = line.split('#',1)[0]; - line = line.rstrip(); - if 'nameserver' in line: - resolvers.append(line.split()[1]) - return resolvers - except IOError: - return [] - -def get_dhcp_search_doms(file): - search_doms = [] - try: - with open(file, 'r') as resolvconf: - for line in resolvconf.readlines(): - line = line.split('#',1)[0]; - line = line.rstrip(); - if 'search' in line: - return re.sub('^search','',line).lstrip().split() - except IOError: - return [] - default_config_data = { 'hostname': 'vyos', 'domain_name': '', @@ -106,158 +85,192 @@ default_config_data = { 'no_dhcp_ns': False } -def get_config(): - conf = Config() - hosts = copy.deepcopy(default_config_data) +# borrowed from: https://github.com/donjajo/py-world/blob/master/resolvconfReader.py, THX! +def get_resolvers(file): + resolv = {} + try: + with open(file, 'r') as resolvconf: + lines = [line.split('#', 1)[0].rstrip() + for line in resolvconf.readlines()] + resolvers = [line.split()[1] + for line in lines if 'nameserver' in line] + domains = [line.split()[1] for line in lines if 'search' in line] + resolv['resolvers'] = resolvers + resolv['domains'] = domains + return resolv + except IOError: + return [] - if conf.exists("system host-name"): - hosts['hostname'] = conf.return_value("system host-name") - if conf.exists("system domain-name"): - hosts['domain_name'] = conf.return_value("system domain-name") - hosts['domain_search'].append(hosts['domain_name']) +def get_config(arguments): + conf = Config() + hosts = copy.deepcopy(default_config_data) - for search in conf.return_values("system domain-search domain"): - hosts['domain_search'].append(search) + if arguments.dhclient: + conf.exists = conf.exists_effective + conf.return_value = conf.return_effective_value + conf.return_values = conf.return_effective_values - if conf.exists("system name-server"): - hosts['nameserver'] = conf.return_values("system name-server") + if conf.exists("system host-name"): + hosts['hostname'] = conf.return_value("system host-name") - if conf.exists("system disable-dhcp-nameservers"): - hosts['no_dhcp_ns'] = conf.exists('system disable-dhcp-nameservers') + if conf.exists("system domain-name"): + hosts['domain_name'] = conf.return_value("system domain-name") + hosts['domain_search'].append(hosts['domain_name']) - ## system static-host-mapping - hosts['static_host_mapping'] = { 'hostnames' : {}} + for search in conf.return_values("system domain-search domain"): + hosts['domain_search'].append(search) - if conf.exists('system static-host-mapping host-name'): - for hn in conf.list_nodes('system static-host-mapping host-name'): - hosts['static_host_mapping']['hostnames'][hn] = { - 'ipaddr' : conf.return_value('system static-host-mapping host-name ' + hn + ' inet'), - 'alias' : '' - } - - if conf.exists('system static-host-mapping host-name ' + hn + ' alias'): - a = conf.return_values('system static-host-mapping host-name ' + hn + ' alias') - hosts['static_host_mapping']['hostnames'][hn]['alias'] = " ".join( conf.return_values('system static-host-mapping host-name ' + hn + ' alias') ) + if conf.exists("system name-server"): + hosts['nameserver'] = conf.return_values("system name-server") - return hosts + if conf.exists("system disable-dhcp-nameservers"): + hosts['no_dhcp_ns'] = conf.exists('system disable-dhcp-nameservers') -def verify(config): - if config is None: - return None + # system static-host-mapping + hosts['static_host_mapping'] = {'hostnames': {}} - # pattern $VAR(@) "^[[:alnum:]][-.[:alnum:]]*[[:alnum:]]$" ; "invalid host name $VAR(@)" - hostname_regex = re.compile("^[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]$") - if not hostname_regex.match(config['hostname']): - raise ConfigError('Invalid host name ' + config["hostname"]) + if conf.exists('system static-host-mapping host-name'): + for hn in conf.list_nodes('system static-host-mapping host-name'): + hosts['static_host_mapping']['hostnames'][hn] = { + 'ipaddr': conf.return_value('system static-host-mapping host-name ' + hn + ' inet'), + 'alias': '' + } - # pattern $VAR(@) "^.{1,63}$" ; "invalid host-name length" - length = len(config['hostname']) - if length < 1 or length > 63: - raise ConfigError('Invalid host-name length, must be less than 63 characters') + if conf.exists('system static-host-mapping host-name ' + hn + ' alias'): + a = conf.return_values( + 'system static-host-mapping host-name ' + hn + ' alias') + hosts['static_host_mapping']['hostnames'][hn]['alias'] = " ".join(a) - # The search list is currently limited to six domains with a total of 256 characters. - # https://linux.die.net/man/5/resolv.conf - if len(config['domain_search']) > 6: - raise ConfigError('The search list is currently limited to six domains') + return hosts - tmp = ' '.join(config['domain_search']) - if len(tmp) > 256: - raise ConfigError('The search list is currently limited to 256 characters') - # static mappings alias hostname - if config['static_host_mapping']['hostnames']: - for hn in config['static_host_mapping']['hostnames']: - if not config['static_host_mapping']['hostnames'][hn]['ipaddr']: - raise ConfigError('IP address required for ' + hn) - for hn_alias in config['static_host_mapping']['hostnames'][hn]['alias'].split(' '): - if not hostname_regex.match(hn_alias) and len (hn_alias) !=0: - raise ConfigError('Invalid hostname alias ' + hn_alias) +def verify(config): + if config is None: + return None + + # pattern $VAR(@) "^[[:alnum:]][-.[:alnum:]]*[[:alnum:]]$" ; "invalid host name $VAR(@)" + hostname_regex = re.compile("^[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]$") + if not hostname_regex.match(config['hostname']): + raise ConfigError('Invalid host name ' + config["hostname"]) + + # pattern $VAR(@) "^.{1,63}$" ; "invalid host-name length" + length = len(config['hostname']) + if length < 1 or length > 63: + raise ConfigError( + 'Invalid host-name length, must be less than 63 characters') + + # The search list is currently limited to six domains with a total of 256 characters. + # https://linux.die.net/man/5/resolv.conf + if len(config['domain_search']) > 6: + raise ConfigError( + 'The search list is currently limited to six domains') + + tmp = ' '.join(config['domain_search']) + if len(tmp) > 256: + raise ConfigError( + 'The search list is currently limited to 256 characters') + + # static mappings alias hostname + if config['static_host_mapping']['hostnames']: + for hn in config['static_host_mapping']['hostnames']: + if not config['static_host_mapping']['hostnames'][hn]['ipaddr']: + raise ConfigError('IP address required for ' + hn) + for hn_alias in config['static_host_mapping']['hostnames'][hn]['alias'].split(' '): + if not hostname_regex.match(hn_alias) and len(hn_alias) != 0: + raise ConfigError('Invalid hostname alias ' + hn_alias) + + return None - return None def generate(config): - if config is None: + if config is None: + return None + + # If "system disable-dhcp-nameservers" is __configured__ all DNS resolvers + # received via dhclient should not be added into the final 'resolv.conf'. + # + # We iterate over every resolver file and retrieve the received nameservers + # for later adjustment of the system nameservers + dhcp_ns = [] + dhcp_sd = [] + for file in glob.glob('/etc/resolv.conf.dhclient-new*'): + for key, value in get_resolvers(file).items(): + ns = [r for r in value if key == 'resolvers'] + dhcp_ns.extend(ns) + sd = [d for d in value if key == 'domains'] + dhcp_sd.extend(sd) + + if not config['no_dhcp_ns']: + config['nameserver'] += dhcp_ns + config['domain_search'] += dhcp_sd + + # We have third party scripts altering /etc/hosts, too. + # One example are the DHCP hostname update scripts thus we need to cache in + # every modification first - so changing domain-name, domain-search or hostname + # during runtime works + old_hosts = "" + with open(config_file_hosts, 'r') as f: + # Skips text before the beginning of our marker. + # NOTE: Marker __MUST__ match the one specified in config_tmpl_hosts + for line in f: + if line.strip() == '### modifications from other scripts should be added below': + break + + for line in f: + # This additional line.strip() filters empty lines + if line.strip(): + old_hosts += line + + # Add an additional newline + old_hosts += '\n' + + tmpl = jinja2.Template(config_tmpl_hosts) + config_text = tmpl.render(config) + + with open(config_file_hosts, 'w') as f: + f.write(config_text) + f.write(old_hosts) + + tmpl = jinja2.Template(config_tmpl_resolv) + config_text = tmpl.render(config) + with open(config_file_resolv, 'w') as f: + f.write(config_text) + return None - # If "system disable-dhcp-nameservers" is __configured__ all DNS resolvers - # received via dhclient should not be added into the final 'resolv.conf'. - # - # We iterate over every resolver file and retrieve the received nameservers - # for later adjustment of the system nameservers - dhcp_ns = [] - for file in glob.glob('/etc/resolv.conf.dhclient-new*'): - for r in get_resolvers(file): - dhcp_ns.append(r) - - if not config['no_dhcp_ns']: - config['nameserver'] += dhcp_ns - for file in glob.glob('/etc/resolv.conf.dhclient-new*'): - config['domain_search'] = get_dhcp_search_doms(file) - - # We have third party scripts altering /etc/hosts, too. - # One example are the DHCP hostname update scripts thus we need to cache in - # every modification first - so changing domain-name, domain-search or hostname - # during runtime works - old_hosts = "" - with open(config_file_hosts, 'r') as f: - # Skips text before the beginning of our marker. - # NOTE: Marker __MUST__ match the one specified in config_tmpl_hosts - for line in f: - if line.strip() == '### modifications from other scripts should be added below': - break - - for line in f: - # This additional line.strip() filters empty lines - if line.strip(): - old_hosts += line - - # Add an additional newline - old_hosts += '\n' - - tmpl = jinja2.Template(config_tmpl_hosts) - config_text = tmpl.render(config) - - with open(config_file_hosts, 'w') as f: - f.write(config_text) - f.write(old_hosts) - - tmpl = jinja2.Template(config_tmpl_resolv) - config_text = tmpl.render(config) - with open(config_file_resolv, 'w') as f: - f.write(config_text) - - return None def apply(config): - if config is None: - return None + if config is None: + return None - fqdn = config['hostname'] - if config['domain_name']: - fqdn += '.' + config['domain_name'] + fqdn = config['hostname'] + if config['domain_name']: + fqdn += '.' + config['domain_name'] - os.system("hostnamectl set-hostname --static {0}".format(fqdn.rstrip('.'))) + os.system("hostnamectl set-hostname --static {0}".format(fqdn.rstrip('.'))) - # Restart services that use the hostname - os.system("systemctl restart rsyslog.service") + # Restart services that use the hostname + os.system("systemctl restart rsyslog.service") - # If SNMP is running, restart it too - if os.system("pgrep snmpd > /dev/null") == 0: - os.system("systemctl restart snmpd.service") + # If SNMP is running, restart it too + if os.system("pgrep snmpd > /dev/null") == 0: + os.system("systemctl restart snmpd.service") - # restart pdns if it is used - if os.system("/usr/bin/rec_control ping >/dev/null 2>&1") == 0: - os.system("/etc/init.d/pdns-recursor restart >/dev/null") + # restart pdns if it is used + if os.system("/usr/bin/rec_control ping >/dev/null 2>&1") == 0: + os.system("/etc/init.d/pdns-recursor restart >/dev/null") + + return None - return None if __name__ == '__main__': - try: - c = get_config() - verify(c) - generate(c) - apply(c) - except ConfigError as e: - print(e) - sys.exit(1) + args = parser.parse_args() + try: + c = get_config(args) + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) -- cgit v1.2.3 From 2a289fda0de755b019d133622340c14a4a723f0f Mon Sep 17 00:00:00 2001 From: Matthias Fetzer Date: Mon, 10 Jun 2019 13:59:19 +0200 Subject: [wireguard] T1428: Add handling of fwmark setting (#70) [wireguard] T1428: correct handling of the fwmark option --- src/conf_mode/wireguard.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/wireguard.py b/src/conf_mode/wireguard.py index e893dba47..b6c1e189b 100755 --- a/src/conf_mode/wireguard.py +++ b/src/conf_mode/wireguard.py @@ -63,6 +63,7 @@ def get_config(): 'lport' : '', 'status' : 'exists', 'state' : 'enabled', + 'fwmark' : 0x00, 'mtu' : '1420', 'peer' : {} } @@ -95,6 +96,9 @@ def get_config(): ### listen port if c.exists(cnf + ' port'): config_data['interfaces'][intfc]['lport'] = c.return_value(cnf + ' port') + ### fwmark + if c.exists(cnf + ' fwmark'): + config_data['interfaces'][intfc]['fwmark'] = c.return_value(cnf + ' fwmark') ### description if c.exists(cnf + ' description'): config_data['interfaces'][intfc]['descr'] = c.return_value(cnf + ' description') @@ -296,6 +300,10 @@ def configure_interface(c, intf): if c['interfaces'][intf]['lport']: wg_config['port'] = c['interfaces'][intf]['lport'] + ## fwmark + if c['interfaces'][intf]['fwmark']: + wg_config['fwmark'] = c['interfaces'][intf]['fwmark'] + ## endpoint if c['interfaces'][intf]['peer'][p]['endpoint']: wg_config['endpoint'] = c['interfaces'][intf]['peer'][p]['endpoint'] @@ -314,6 +322,7 @@ def configure_interface(c, intf): ### assemble wg command cmd = "sudo wg set " + intf cmd += " listen-port " + str(wg_config['port']) + cmd += " fwmark " + str(wg_config['fwmark']) cmd += " private-key " + wg_config['private-key'] cmd += " peer " + wg_config['pubkey'] cmd += " preshared-key " + wg_config['psk'] -- cgit v1.2.3 From 3b9bfe322fd4a7d652b25b28cbcd4825fee0ea4b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Jun 2019 02:02:25 +0200 Subject: DHCPDv6 T1433: fix wrong lease file name A wrong lease file caused the show command to fail: vyos@vyos:~$ show dhcpv6 server leases Traceback (most recent call last): File "/usr/libexec/vyos/op_mode/show_dhcpv6.py", line 81, in leases = get_leases(lease_file, state='active') File "/usr/libexec/vyos/op_mode/show_dhcpv6.py", line 44, in get_leases leases = IscDhcpLeases(lease_file).get() File "/usr/lib/python3/dist-packages/isc_dhcp_leases/iscdhcpleases.py", line 110, in get with open(self.filename) as lease_file: FileNotFoundError: [Errno 2] No such file or directory: '/config/dhcpdv6.leases' --- src/conf_mode/dhcpv6_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index bb3e6e90d..8199c6a53 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -28,7 +28,7 @@ from vyos.config import Config from vyos import ConfigError config_file = r'/etc/dhcp/dhcpd6.conf' -lease_file = r'/config/dhcpd6.leases' +lease_file = r'/config/dhcpdv6.leases' daemon_config_file = r'/etc/default/isc-dhcpv6-server' # Please be careful if you edit the template. -- cgit v1.2.3 From adaa9b78e2fb0c7da58ca6c09934b3e3cff44795 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Jun 2019 02:03:57 +0200 Subject: DHCPDv6 T1433: rename daemon configuration file ... to have the same pattern as the DHCPDv6 lease file --- src/conf_mode/dhcpv6_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 8199c6a53..5430097de 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -27,7 +27,7 @@ import vyos.validate from vyos.config import Config from vyos import ConfigError -config_file = r'/etc/dhcp/dhcpd6.conf' +config_file = r'/etc/dhcp/dhcpdv6.conf' lease_file = r'/config/dhcpdv6.leases' daemon_config_file = r'/etc/default/isc-dhcpv6-server' -- cgit v1.2.3 From b04a9791226f7953cfa740804ec0d43745605f49 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 16 Jun 2019 15:27:03 +0200 Subject: T1439: remove quotes around dhcp6.client-id --- src/conf_mode/dhcpv6_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 5430097de..aa9c35fa1 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -94,7 +94,7 @@ shared-network {{ network.name }} { {%- for host in subnet.static_mapping %} {% if not host.disabled -%} host {{ network.name }}_{{ host.name }} { - host-identifier option dhcp6.client-id "{{ host.client_identifier }}"; + host-identifier option dhcp6.client-id {{ host.client_identifier }}; fixed-address6 {{ host.ipv6_address }}; } {%- endif %} -- cgit v1.2.3 From 9b4447f039655b9e24187302b9c4d9bb2116aa99 Mon Sep 17 00:00:00 2001 From: hagbard Date: Mon, 17 Jun 2019 08:38:37 -0700 Subject: [pppoe-server] T1408 - improve verify() function to enable IPv6 only deployments --- src/conf_mode/accel_pppoe.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/accel_pppoe.py b/src/conf_mode/accel_pppoe.py index 7a9878a1d..9d2efb0bc 100755 --- a/src/conf_mode/accel_pppoe.py +++ b/src/conf_mode/accel_pppoe.py @@ -83,17 +83,19 @@ master=1 [client-ip-range] disable +{% if ppp_gw %} [ip-pool] gw-ip-address={{ppp_gw}} {% if client_ip_pool %} {{client_ip_pool}} -{% endif %} +{% endif -%} {% if client_ip_subnets %} {% for sn in client_ip_subnets %} {{sn}} {% endfor %} {% endif %} +{% endif -%} {% if client_ipv6_pool %} [ipv6-pool] @@ -550,18 +552,14 @@ def verify(c): if c['authentication']['radiussrv'][rsrv]['secret'] == None: raise ConfigError('radius server ' + rsrv + ' needs a secret configured') - ### local ippool and gateway settings - - if not c['ppp_gw']: - raise ConfigError('pppoe-server local-ip required') - - if not c['client_ip_subnets'] and not c['client_ip_pool']: - print ("Warning: No pppoe client IP pool defined") + ### local ippool and gateway settings config checks - ### activate as soon as it is clear what to do migrate or depricate. - #if c['client_ip_pool']: - # print ("Warning: client-ip-pool (start|stop) is depricated, please use client-ip-pool subnet") - # sl.syslog(sl.LOG_NOTICE, "client-ip-pool start stop is depricated, please use client-ip-pool subnet") + if c['client_ip_subnets'] or c['client_ip_pool']: + if not c['ppp_gw']: + raise ConfigError('pppoe-server local-ip required') + + if c['ppp_gw'] and not c['client_ip_subnets'] and not c['client_ip_pool']: + print ("Warning: No pppoe client IPv4 pool defined") def generate(c): if c == None: -- cgit v1.2.3 From 0f354688d7bd63b63fb91faf17a38c77fb05f660 Mon Sep 17 00:00:00 2001 From: hagbard Date: Mon, 17 Jun 2019 11:10:33 -0700 Subject: [syslog/hostname.py] T1394 - syslog systemd and host_name.py race condition - checking if the hostname has changed, otherwise the script and systemd try to restart rsyslogd at the same time, at the end it's not started at all. --- src/conf_mode/host_name.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 621ccd7e0..0d03fd41c 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -248,10 +248,15 @@ def apply(config): if config['domain_name']: fqdn += '.' + config['domain_name'] + # rsyslog runs into a race condition at boot time with systemd + # restart rsyslog only if the hostname changed. + hn = subprocess.check_output(['hostnamectl','--static']).decode().strip() + os.system("hostnamectl set-hostname --static {0}".format(fqdn.rstrip('.'))) # Restart services that use the hostname - os.system("systemctl restart rsyslog.service") + if hn != fqdn: + os.system("systemctl restart rsyslog.service") # If SNMP is running, restart it too if os.system("pgrep snmpd > /dev/null") == 0: -- cgit v1.2.3 From 0037287a8e6a7ddd6d4a8101804d9cc0b8b3e70f Mon Sep 17 00:00:00 2001 From: Kim Hagen Date: Tue, 18 Jun 2019 16:08:08 +0200 Subject: [ config ] T1447: Python subprocess called without import in host_name.py --- src/conf_mode/host_name.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 0d03fd41c..13b2b98ae 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -25,6 +25,7 @@ import re import sys import copy import glob +import subprocess import argparse import jinja2 @@ -250,13 +251,13 @@ def apply(config): # rsyslog runs into a race condition at boot time with systemd # restart rsyslog only if the hostname changed. - hn = subprocess.check_output(['hostnamectl','--static']).decode().strip() + hn = subprocess.check_output(['hostnamectl', '--static']).decode().strip() os.system("hostnamectl set-hostname --static {0}".format(fqdn.rstrip('.'))) # Restart services that use the hostname if hn != fqdn: - os.system("systemctl restart rsyslog.service") + os.system("systemctl restart rsyslog.service") # If SNMP is running, restart it too if os.system("pgrep snmpd > /dev/null") == 0: -- cgit v1.2.3 From 85c9e0200a4619f0388b7fd7ba9a03f4be933ef5 Mon Sep 17 00:00:00 2001 From: hagbard Date: Tue, 18 Jun 2019 15:07:41 -0700 Subject: [pppoe-server] T1452 - add vendor option to shaper --- interface-definitions/pppoe-server.xml | 5 +++++ src/conf_mode/accel_pppoe.py | 6 ++++++ 2 files changed, 11 insertions(+) (limited to 'src/conf_mode') diff --git a/interface-definitions/pppoe-server.xml b/interface-definitions/pppoe-server.xml index 1190cd9ff..18b0e649c 100644 --- a/interface-definitions/pppoe-server.xml +++ b/interface-definitions/pppoe-server.xml @@ -194,6 +194,11 @@ Specifies which radius attribute contains rate information. (default is Filter-Id) + + + Specifies the vendor dictionary. (dictionary needs to be in /usr/share/accel-ppp/radius) + + Enables Bandwidth shaping via RADIUS diff --git a/src/conf_mode/accel_pppoe.py b/src/conf_mode/accel_pppoe.py index 9d2efb0bc..3c7759b17 100755 --- a/src/conf_mode/accel_pppoe.py +++ b/src/conf_mode/accel_pppoe.py @@ -173,6 +173,9 @@ verbose=1 [shaper] verbose=1 attr={{authentication['radiusopt']['shaper']['attr']}} +{% if authentication['radiusopt']['shaper']['vendor'] %} +vendor={{authentication['radiusopt']['shaper']['vendor']}} +{% endif -%} {% endif -%} {% endif %} @@ -485,6 +488,9 @@ def get_config(): config_data['authentication']['radiusopt']['shaper'] = { 'attr' : c.return_value('authentication radius-settings rate-limit attribute') } + if c.exists('authentication radius-settings rate-limit vendor'): + config_data['authentication']['radiusopt']['shaper']['vendor'] = c.return_value('authentication radius-settings rate-limit vendor') + if c.exists('mtu'): config_data['mtu'] = c.return_value('mtu') -- cgit v1.2.3 From 06e6ae3bac7cd39341c0b19b570020649d725344 Mon Sep 17 00:00:00 2001 From: Kim Hagen Date: Thu, 20 Jun 2019 10:33:56 +0200 Subject: T1458: Regression in 1.2.1-S2 hostname & logging --- src/conf_mode/host_name.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 13b2b98ae..b0a4648c7 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -42,7 +42,8 @@ config_file_resolv = '/etc/resolv.conf' config_tmpl_hosts = """ ### Autogenerated by host_name.py ### -127.0.0.1 localhost {{ hostname }}{% if domain_name %}.{{ domain_name }}{% endif %} +127.0.0.1 localhost +127.0.1.1 {{ hostname }}{% if domain_name %}.{{ domain_name }}{% endif %} # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback -- cgit v1.2.3 From efb1a1c88f436a3704c4ca6e15b65aeded4b9654 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 20 Jun 2019 22:14:36 +0200 Subject: firewall: T1461: deleting 'firewall options' causes Python TypeError [ firewall options interface wg01 ] Traceback (most recent call last): File "/usr/libexec/vyos/conf_mode/firewall_options.py", line 139, in apply(c) File "/usr/libexec/vyos/conf_mode/firewall_options.py", line 97, in apply if tcp['new_chain4']: TypeError: 'NoneType' object is not subscriptable delete [ firewall options ] failed delete [ firewall ] failed Commit failed --- src/conf_mode/firewall_options.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/firewall_options.py b/src/conf_mode/firewall_options.py index e2c306904..2be80cdbf 100755 --- a/src/conf_mode/firewall_options.py +++ b/src/conf_mode/firewall_options.py @@ -32,7 +32,8 @@ def get_config(): opts = copy.deepcopy(default_config_data) conf = Config() if not conf.exists('firewall options'): - return None + # bail out early + return opts else: conf.set_level('firewall options') -- cgit v1.2.3 From ecbe6c1c1b87792048512e3aa4913c1ce5b75c82 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Jun 2019 19:59:33 +0200 Subject: bfd: T1183: initial CLI implementation vyos@vyos# show protocols bfd peer 172.18.202.10 { local-address 172.18.201.10 local-interface eth0.201 shutdown } peer 172.18.202.12 { shutdown } --- interface-definitions/protocols-bfd.xml | 58 +++++++++++++++++++++++++ src/conf_mode/protocols_bfd.py | 76 +++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 interface-definitions/protocols-bfd.xml create mode 100755 src/conf_mode/protocols_bfd.py (limited to 'src/conf_mode') diff --git a/interface-definitions/protocols-bfd.xml b/interface-definitions/protocols-bfd.xml new file mode 100644 index 000000000..c8b25eb2d --- /dev/null +++ b/interface-definitions/protocols-bfd.xml @@ -0,0 +1,58 @@ + + + + + + + + Bidirectional Forwarding Detection (BFD) + 820 + + + + + Configures a new BFD peer to listen and talk to + + ipv4 + BFD peer IPv4 address + + + ipv6 + BFD peer IPv6 address + + + + + + Local interface to bind our peer listener to + + + + + + + + Local address to bind our peer listener to + + ipv4 + Local IPv4 address used to connect to the peer + + + ipv6 + Local IPv6 address used to connect to the peer + + + + + + Disable this peer + + + + + + + + + + diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py new file mode 100755 index 000000000..7e137c484 --- /dev/null +++ b/src/conf_mode/protocols_bfd.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import copy +from vyos.config import Config + +default_config_data = { + 'peers': [] +} + +def get_config(): + bfd = copy.deepcopy(default_config_data) + conf = Config() + if not conf.exists('protocols bfd'): + return None + else: + conf.set_level('protocols bfd') + + for peer in conf.list_nodes('peer'): + conf.set_level('protocols bfd peer {0}'.format(peer)) + bfd_peer = { + 'peer': peer, + 'shutdown': False, + 'local-interface': '', + 'local-address': '', + } + + # Check if individual peer is disabled + if conf.exists('shutdown'): + bfd_peer['shutdown'] = True + + # Check if peer has a local source interface configured + if conf.exists('local-interface'): + bfd_peer['local-interface'] = conf.return_value('local-interface') + + # Check if peer has a local source address configured - this is mandatory for IPv6 + if conf.exists('local-address'): + bfd_peer['local-address'] = conf.return_value('local-address') + + bfd['peers'].append(bfd_peer) + + print(bfd) + return bfd + +def verify(bfd): + return None + +def generate(bfd): + return None + +def apply(bfd): + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) -- cgit v1.2.3 From 4fa93aefd1e4c525267f90b5fd7797157946c9bd Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 16 Jun 2019 20:20:09 +0200 Subject: bfd: T1183: IPv6 peers require explicit local address/interface --- src/conf_mode/protocols_bfd.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 7e137c484..51af67ff6 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -15,7 +15,11 @@ # along with this program. If not, see . # +import sys import copy +import vyos.validate + +from vyos import ConfigError from vyos.config import Config default_config_data = { @@ -33,7 +37,7 @@ def get_config(): for peer in conf.list_nodes('peer'): conf.set_level('protocols bfd peer {0}'.format(peer)) bfd_peer = { - 'peer': peer, + 'remote': peer, 'shutdown': False, 'local-interface': '', 'local-address': '', @@ -53,16 +57,36 @@ def get_config(): bfd['peers'].append(bfd_peer) - print(bfd) return bfd def verify(bfd): + if bfd is None: + return None + + for peer in bfd['peers']: + # Bail out early if peer is shutdown + if peer['shutdown']: + continue + + # IPv6 peers require an explicit local address/interface combination + if vyos.validate.is_ipv6(peer['remote']): + if not (peer['local-interface'] and peer['local-address']): + raise ConfigError("BFD IPv6 peers require explicit local address/interface setting") + + return None def generate(bfd): + if bfd is None: + return None + return None def apply(bfd): + if bfd is None: + return None + + print(bfd) return None if __name__ == '__main__': -- cgit v1.2.3 From 62ca0f55506245865dcc14fd95d68d9a3482df7b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 21 Jun 2019 21:02:13 +0200 Subject: bfd: T1183: first working FRR bfd peer configuration --- src/conf_mode/protocols_bfd.py | 54 +++++++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 11 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 51af67ff6..08d3991ff 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -16,31 +16,56 @@ # import sys +import jinja2 import copy +import os import vyos.validate from vyos import ConfigError from vyos.config import Config +config_file = r'/tmp/bfd.frr' + +# Please be careful if you edit the template. +config_tmpl = """ +! +bfd +{% for peer in old_peers -%} + no peer {{ peer }} +{% endfor -%} +! +{% for peer in new_peers -%} + peer {{ peer.remote }}{% if peer.local_address %} local-address {{ peer.local_address }}{% endif %}{% if peer.local_interface %} interface {{ peer.local_interface }}{% endif %} + {% if not peer.shutdown %}no {% endif %}shutdown +{% endfor -%} +! +""" + default_config_data = { - 'peers': [] + 'new_peers': [], + 'old_peers' : [] } def get_config(): bfd = copy.deepcopy(default_config_data) conf = Config() - if not conf.exists('protocols bfd'): + if not (conf.exists('protocols bfd') or conf.exists_effective('protocols bfd')): return None else: conf.set_level('protocols bfd') + # as we have to use vtysh to talk to FRR we also need to know + # which peers are gone due to a config removal - thus we read in + # all peers (active or to delete) + bfd['old_peers'] = conf.list_effective_nodes('peer') + for peer in conf.list_nodes('peer'): conf.set_level('protocols bfd peer {0}'.format(peer)) bfd_peer = { 'remote': peer, 'shutdown': False, - 'local-interface': '', - 'local-address': '', + 'local_interface': '', + 'local_address': '', } # Check if individual peer is disabled @@ -49,13 +74,13 @@ def get_config(): # Check if peer has a local source interface configured if conf.exists('local-interface'): - bfd_peer['local-interface'] = conf.return_value('local-interface') + bfd_peer['local_interface'] = conf.return_value('local-interface') # Check if peer has a local source address configured - this is mandatory for IPv6 if conf.exists('local-address'): - bfd_peer['local-address'] = conf.return_value('local-address') + bfd_peer['local_address'] = conf.return_value('local-address') - bfd['peers'].append(bfd_peer) + bfd['new_peers'].append(bfd_peer) return bfd @@ -63,17 +88,16 @@ def verify(bfd): if bfd is None: return None - for peer in bfd['peers']: + for peer in bfd['new_peers']: # Bail out early if peer is shutdown if peer['shutdown']: continue # IPv6 peers require an explicit local address/interface combination if vyos.validate.is_ipv6(peer['remote']): - if not (peer['local-interface'] and peer['local-address']): + if not (peer['local_interface'] and peer['local_address']): raise ConfigError("BFD IPv6 peers require explicit local address/interface setting") - return None def generate(bfd): @@ -86,7 +110,15 @@ def apply(bfd): if bfd is None: return None - print(bfd) + tmpl = jinja2.Template(config_tmpl) + config_text = tmpl.render(bfd) + with open(config_file, 'w') as f: + f.write(config_text) + + os.system("sudo vtysh -d bfdd -f " + config_file) + if os.path.exists(config_file): + os.remove(config_file) + return None if __name__ == '__main__': -- cgit v1.2.3 From 1b1f6b20226c92e4beba171159ead8fb21713484 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 21 Jun 2019 21:07:45 +0200 Subject: bfd: T1183: add support for multihop multihop tells the BFD daemon that we should expect packets with TTL less than 254 (because it will take more than one hop) and to listen on the multihop port (4784). When using multi-hop mode echo-mode will not work (see RFC 5883 section 3). --- interface-definitions/protocols-bfd.xml | 6 ++++++ src/conf_mode/protocols_bfd.py | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/protocols-bfd.xml b/interface-definitions/protocols-bfd.xml index c8b25eb2d..91f0665f9 100644 --- a/interface-definitions/protocols-bfd.xml +++ b/interface-definitions/protocols-bfd.xml @@ -49,6 +49,12 @@ + + + Allow this BFD peer to not be directly connected + + + diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 08d3991ff..92fae990e 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -35,7 +35,7 @@ bfd {% endfor -%} ! {% for peer in new_peers -%} - peer {{ peer.remote }}{% if peer.local_address %} local-address {{ peer.local_address }}{% endif %}{% if peer.local_interface %} interface {{ peer.local_interface }}{% endif %} + peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.local_address %} local-address {{ peer.local_address }}{% endif %}{% if peer.local_interface %} interface {{ peer.local_interface }}{% endif %} {% if not peer.shutdown %}no {% endif %}shutdown {% endfor -%} ! @@ -66,6 +66,7 @@ def get_config(): 'shutdown': False, 'local_interface': '', 'local_address': '', + 'multihop': False } # Check if individual peer is disabled @@ -80,6 +81,12 @@ def get_config(): if conf.exists('local-address'): bfd_peer['local_address'] = conf.return_value('local-address') + # Tell BFD daemon that we should expect packets with TTL less than 254 + # (because it will take more than one hop) and to listen on the multihop + # port (4784) + if conf.exists('multihop'): + bfd_peer['multihop'] = True + bfd['new_peers'].append(bfd_peer) return bfd -- cgit v1.2.3 From c2a8c1a22f432265c73606106046c02e995eb630 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 22 Jun 2019 14:44:45 +0200 Subject: bfd: T1183: adjust CLI syntax for source address/interface Place address/interface under new source node. vyis@vyos# show protocols bfd peer 1.1.1.1 { source { address 1.2.3.4 interface eth0.201 } } --- interface-definitions/protocols-bfd.xml | 45 +++++++++++++++++++-------------- src/conf_mode/protocols_bfd.py | 16 ++++++------ 2 files changed, 34 insertions(+), 27 deletions(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/protocols-bfd.xml b/interface-definitions/protocols-bfd.xml index 91f0665f9..ab8c9e233 100644 --- a/interface-definitions/protocols-bfd.xml +++ b/interface-definitions/protocols-bfd.xml @@ -22,27 +22,34 @@ - + - Local interface to bind our peer listener to - - - + Bind listener to specifid interface/address, mandatory for IPv6 - - - - Local address to bind our peer listener to - - ipv4 - Local IPv4 address used to connect to the peer - - - ipv6 - Local IPv6 address used to connect to the peer - - - + + + + Local interface to bind our peer listener to + + + + + + + + Local address to bind our peer listener to + + ipv4 + Local IPv4 address used to connect to the peer + + + ipv6 + Local IPv6 address used to connect to the peer + + + + + Disable this peer diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 92fae990e..6d2b8382a 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -35,7 +35,7 @@ bfd {% endfor -%} ! {% for peer in new_peers -%} - peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.local_address %} local-address {{ peer.local_address }}{% endif %}{% if peer.local_interface %} interface {{ peer.local_interface }}{% endif %} + peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.src_addr %} local-address {{ peer.src_addr }}{% endif %}{% if peer.src_if %} interface {{ peer.src_if }}{% endif %} {% if not peer.shutdown %}no {% endif %}shutdown {% endfor -%} ! @@ -64,8 +64,8 @@ def get_config(): bfd_peer = { 'remote': peer, 'shutdown': False, - 'local_interface': '', - 'local_address': '', + 'src_if': '', + 'src_addr': '', 'multihop': False } @@ -74,12 +74,12 @@ def get_config(): bfd_peer['shutdown'] = True # Check if peer has a local source interface configured - if conf.exists('local-interface'): - bfd_peer['local_interface'] = conf.return_value('local-interface') + if conf.exists('source interface'): + bfd_peer['src_if'] = conf.return_value('source interface') # Check if peer has a local source address configured - this is mandatory for IPv6 - if conf.exists('local-address'): - bfd_peer['local_address'] = conf.return_value('local-address') + if conf.exists('source address'): + bfd_peer['src_addr'] = conf.return_value('source address') # Tell BFD daemon that we should expect packets with TTL less than 254 # (because it will take more than one hop) and to listen on the multihop @@ -102,7 +102,7 @@ def verify(bfd): # IPv6 peers require an explicit local address/interface combination if vyos.validate.is_ipv6(peer['remote']): - if not (peer['local_interface'] and peer['local_address']): + if not (peer['src_if'] and peer['src_addr']): raise ConfigError("BFD IPv6 peers require explicit local address/interface setting") return None -- cgit v1.2.3 From 4e4b945b6b88308fe8938663ad12efebf98e08fd Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 22 Jun 2019 14:51:44 +0200 Subject: bfd: T1183: add support to configure detection multiplier Configures the detection multiplier to determine packet loss. The remote transmission interval will be multiplied by this value to determine the connection loss detection timer. The default value is 3. Example: when the local system has detect-multiplier 3 and the remote system has transmission interval 300, the local system will detect failures only after 900 milliseconds without receiving packets. --- interface-definitions/protocols-bfd.xml | 12 ++++++++++++ src/conf_mode/protocols_bfd.py | 8 ++++++++ 2 files changed, 20 insertions(+) (limited to 'src/conf_mode') diff --git a/interface-definitions/protocols-bfd.xml b/interface-definitions/protocols-bfd.xml index ab8c9e233..a731334a0 100644 --- a/interface-definitions/protocols-bfd.xml +++ b/interface-definitions/protocols-bfd.xml @@ -50,6 +50,18 @@ + + + Multiplier to determine packet loss + + 2-255 + Remote transmission interval will be multiplied by this value + + + + + + Disable this peer diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 6d2b8382a..96a41de11 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -36,6 +36,7 @@ bfd ! {% for peer in new_peers -%} peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.src_addr %} local-address {{ peer.src_addr }}{% endif %}{% if peer.src_if %} interface {{ peer.src_if }}{% endif %} + detect-multiplier {{ peer.multiplier }} {% if not peer.shutdown %}no {% endif %}shutdown {% endfor -%} ! @@ -66,6 +67,7 @@ def get_config(): 'shutdown': False, 'src_if': '', 'src_addr': '', + 'multiplier': '3', 'multihop': False } @@ -81,6 +83,12 @@ def get_config(): if conf.exists('source address'): bfd_peer['src_addr'] = conf.return_value('source address') + # Configures the detection multiplier to determine packet loss. The remote + # transmission interval will be multiplied by this value to determine the + # connection loss detection timer. The default value is 3. + if conf.exists('multiplier'): + bfd_peer['multiplier'] = conf.return_value('multiplier') + # Tell BFD daemon that we should expect packets with TTL less than 254 # (because it will take more than one hop) and to listen on the multihop # port (4784) -- cgit v1.2.3 From 67a35cfc37a5cc34a1b874f69626802ec3d35f94 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 22 Jun 2019 14:59:27 +0200 Subject: bfd: T1183: multihop doesn't accept interface names --- src/conf_mode/protocols_bfd.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 96a41de11..3d60f5358 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -111,7 +111,12 @@ def verify(bfd): # IPv6 peers require an explicit local address/interface combination if vyos.validate.is_ipv6(peer['remote']): if not (peer['src_if'] and peer['src_addr']): - raise ConfigError("BFD IPv6 peers require explicit local address/interface setting") + raise ConfigError('BFD IPv6 peers require explicit local address/interface setting') + + # multihop doesn't accept interface names + if peer['multihop'] and peer['src_if']: + raise ConfigError('multihop does not accept interface names') + return None -- cgit v1.2.3 From 8b8c7424c90275a3814e7c17939cfb3a66145a19 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 22 Jun 2019 15:18:31 +0200 Subject: bfd: T1183: add rx/tx interval configuration vyos@vyos# show protocols bfd { peer 1.1.1.1 { interval { receive 400 transmit 300 } } } --- interface-definitions/protocols-bfd.xml | 31 +++++++++++++++++++++++++++++++ src/conf_mode/protocols_bfd.py | 14 ++++++++++++++ 2 files changed, 45 insertions(+) (limited to 'src/conf_mode') diff --git a/interface-definitions/protocols-bfd.xml b/interface-definitions/protocols-bfd.xml index a731334a0..0e92a7ddd 100644 --- a/interface-definitions/protocols-bfd.xml +++ b/interface-definitions/protocols-bfd.xml @@ -62,6 +62,37 @@ + + + Configure timer intervals + + + + + Minimum interval of receiving control packets + + 10-60000 + Interval in milliseconds + + + + + + + + + Minimum interval of transmitting control packets + + 10-60000 + Interval in milliseconds + + + + + + + + Disable this peer diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 3d60f5358..2f494c2e4 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -37,6 +37,8 @@ bfd {% for peer in new_peers -%} peer {{ peer.remote }}{% if peer.multihop %} multihop{% endif %}{% if peer.src_addr %} local-address {{ peer.src_addr }}{% endif %}{% if peer.src_if %} interface {{ peer.src_if }}{% endif %} detect-multiplier {{ peer.multiplier }} + receive-interval {{ peer.rx_interval }} + transmit-interval {{ peer.tx_interval }} {% if not peer.shutdown %}no {% endif %}shutdown {% endfor -%} ! @@ -68,6 +70,8 @@ def get_config(): 'src_if': '', 'src_addr': '', 'multiplier': '3', + 'rx_interval': '300', + 'tx_interval': '300', 'multihop': False } @@ -95,6 +99,16 @@ def get_config(): if conf.exists('multihop'): bfd_peer['multihop'] = True + # Configures the minimum interval that this system is capable of receiving + # control packets. The default value is 300 milliseconds. + if conf.exists('interval receive'): + bfd_peer['rx_interval'] = conf.return_value('interval receive') + + # The minimum transmission interval (less jitter) that this system wants + # to use to send BFD control packets. + if conf.exists('interval transmit'): + bfd_peer['tx_interval'] = conf.return_value('interval transmit') + bfd['new_peers'].append(bfd_peer) return bfd -- cgit v1.2.3 From 7d5b78242859540955006e11d8ca08b463244950 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 22 Jun 2019 15:21:08 +0200 Subject: bfd: T1183: move "multiplier" configuration node to "interval multiplier" --- interface-definitions/protocols-bfd.xml | 24 ++++++++++++------------ src/conf_mode/protocols_bfd.py | 12 ++++++------ 2 files changed, 18 insertions(+), 18 deletions(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/protocols-bfd.xml b/interface-definitions/protocols-bfd.xml index 0e92a7ddd..47d5bf97d 100644 --- a/interface-definitions/protocols-bfd.xml +++ b/interface-definitions/protocols-bfd.xml @@ -50,18 +50,6 @@ - - - Multiplier to determine packet loss - - 2-255 - Remote transmission interval will be multiplied by this value - - - - - - Configure timer intervals @@ -91,6 +79,18 @@ + + + Multiplier to determine packet loss + + 2-255 + Remote transmission interval will be multiplied by this value + + + + + + diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 2f494c2e4..04549f4b4 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -87,12 +87,6 @@ def get_config(): if conf.exists('source address'): bfd_peer['src_addr'] = conf.return_value('source address') - # Configures the detection multiplier to determine packet loss. The remote - # transmission interval will be multiplied by this value to determine the - # connection loss detection timer. The default value is 3. - if conf.exists('multiplier'): - bfd_peer['multiplier'] = conf.return_value('multiplier') - # Tell BFD daemon that we should expect packets with TTL less than 254 # (because it will take more than one hop) and to listen on the multihop # port (4784) @@ -109,6 +103,12 @@ def get_config(): if conf.exists('interval transmit'): bfd_peer['tx_interval'] = conf.return_value('interval transmit') + # Configures the detection multiplier to determine packet loss. The remote + # transmission interval will be multiplied by this value to determine the + # connection loss detection timer. The default value is 3. + if conf.exists('interval multiplier'): + bfd_peer['multiplier'] = conf.return_value('interval multiplier') + bfd['new_peers'].append(bfd_peer) return bfd -- cgit v1.2.3 From 690ae8bf526b6d45997bedf5e856f858ad251658 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sat, 22 Jun 2019 01:52:01 +0200 Subject: T1433: fix also filenames in /etc/default/isc-dhcpv6-server --- src/conf_mode/dhcpv6_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index aa9c35fa1..37da3ef25 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -112,8 +112,8 @@ daemon_tmpl = """ # sourced by /etc/init.d/isc-dhcpv6-server -DHCPD_CONF=/etc/dhcp/dhcpd6.conf -DHCPD_PID=/var/run/dhcpd6.pid +DHCPD_CONF=/etc/dhcp/dhcpdv6.conf +DHCPD_PID=/var/run/dhcpdv6.pid OPTIONS="-6 -lf {{ lease_file }}" INTERFACES="" """ -- cgit v1.2.3 From 7773ad30bd940ffb5144224d61dc3354396f2c8b Mon Sep 17 00:00:00 2001 From: qiuchengxuan Date: Sat, 22 Jun 2019 21:51:17 +0800 Subject: [pdns-recursor] T1469 - replace forward-zones with forward-zones-recurse (#75) forward-zones-recurse behaves identically to dnsmasq server option in legacy vyos 1.1.8, while forward-zones option disallow recursive name resolving, which leads to dns lookup failure --- src/conf_mode/dns_forwarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 7559a0af6..0ce2eee2c 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -63,7 +63,7 @@ local-address={{ listen_on | join(',') }} # domain ... server ... {% if domains -%} -forward-zones={% for d in domains %} +forward-zones-recurse={% for d in domains %} {{ d.name }}={{ d.servers | join(";") }} {{- "," if not loop.last -}} {% endfor %} -- cgit v1.2.3 From 89995dcec3a2a6b9112f11cf1c9e4a1bcb4d66d6 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 23 Jun 2019 17:01:12 +0200 Subject: T1470: improve output of "show dhcpv6 server leases" - change DUID to IAID_DUID - format IAID_DUID as colon-separated hex list - implement functions: pool, sort, state - add op-mode definitions for pool, sort, state - add columns: State, Type, Last communication, Pool - implement json output - implement completionHelp function --- op-mode-definitions/dhcp.xml | 29 ++++++++++++ src/conf_mode/dhcpv6_server.py | 3 ++ src/op_mode/show_dhcpv6.py | 104 ++++++++++++++++++++++++++++++++++------- 3 files changed, 120 insertions(+), 16 deletions(-) (limited to 'src/conf_mode') diff --git a/op-mode-definitions/dhcp.xml b/op-mode-definitions/dhcp.xml index 85403af6f..989c8274a 100644 --- a/op-mode-definitions/dhcp.xml +++ b/op-mode-definitions/dhcp.xml @@ -59,6 +59,35 @@ Show DHCPv6 server leases sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases + + + + Show DHCPv6 server leases for a specific pool + + + + + sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --pool $6 + + + + Show DHCPv6 server leases sorted by the specified key + + + + + sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --sort $6 + + + + Show DHCPv6 server leases with a specific state + + + + + sudo ${vyos_op_scripts_dir}/show_dhcpv6.py --leases --state $6 + + diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index 37da3ef25..f5117de53 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -101,6 +101,9 @@ shared-network {{ network.name }} { {%- endfor %} } {%- endfor %} + on commit { + set shared-networkname = "{{ network.name }}"; + } } {%- endif %} {% endfor %} diff --git a/src/op_mode/show_dhcpv6.py b/src/op_mode/show_dhcpv6.py index bf73b92ea..f1f5a6a55 100755 --- a/src/op_mode/show_dhcpv6.py +++ b/src/op_mode/show_dhcpv6.py @@ -20,11 +20,42 @@ import argparse import ipaddress import tabulate import sys +import collections from vyos.config import Config from isc_dhcp_leases import Lease, IscDhcpLeases lease_file = "/config/dhcpdv6.leases" +pool_key = "shared-networkname" + +lease_display_fields = collections.OrderedDict() +lease_display_fields['ip'] = 'IPv6 address' +lease_display_fields['state'] = 'State' +lease_display_fields['last_comm'] = 'Last communication' +lease_display_fields['expires'] = 'Lease expiration' +lease_display_fields['type'] = 'Type' +lease_display_fields['pool'] = 'Pool' +lease_display_fields['iaid_duid'] = 'IAID_DUID' + +lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup'] + +def in_pool(lease, pool): + if pool_key in lease.sets: + if lease.sets[pool_key] == pool: + return True + + return False + +def format_hex_string(in_str): + out_str = "" + + # if input is divisible by 2, add : every 2 chars + if len(in_str) > 0 and len(in_str) % 2 == 0: + out_str = ':'.join(a+b for a,b in zip(in_str[::2], in_str[1::2])) + else: + out_str = in_str + + return out_str def get_lease_data(lease): data = {} @@ -35,27 +66,55 @@ def get_lease_data(lease): except: data["expires"] = "" - data["duid"] = lease.host_identifier_string + try: + data["last_comm"] = lease.last_communication.strftime("%Y/%m/%d %H:%M:%S") + except: + data["last_comm"] = "" + + # isc-dhcp records lease declarations as ia_{na|ta|pd} IAID_DUID {...} + # where IAID_DUID is the combined IAID and DUID + data["iaid_duid"] = format_hex_string(lease.host_identifier_string) + + lease_types_long = {"na": "non-temporary", "ta": "temporary", "pd": "prefix delegation"} + data["type"] = lease_types_long[lease.type] + + data["state"] = lease.binding_state data["ip"] = lease.ip + try: + data["pool"] = lease.sets[pool_key] + except: + data["pool"] = "" + return data -def get_leases(leases, state=None): +def get_leases(leases, state, pool=None, sort='ip'): leases = IscDhcpLeases(lease_file).get() - if state is not None: - leases = list(filter(lambda x: x.binding_state == 'active', leases)) + if state != 'all': + leases = list(filter(lambda x: x.binding_state == state, leases)) - return list(map(get_lease_data, leases)) + # filter lease by pool name + if pool is not None: + leases = list(filter(lambda x: in_pool(x, pool), leases)) -def show_leases(leases): - headers = ["IPv6 address", "Lease expiration", "DUID"] + leases = list(map(get_lease_data, leases)) + if sort == 'ip': + leases = sorted(leases, key = lambda k: int(ipaddress.IPv6Address(k['ip']))) + else: + leases = sorted(leases, key = lambda k: k[sort]) + + return leases +def show_leases(leases): lease_list = [] for l in leases: - lease_list.append([l["ip"], l["expires"], l["duid"]]) + lease_list_params = [] + for k in lease_display_fields.keys(): + lease_list_params.append(l[k]) + lease_list.append(lease_list_params) - output = tabulate.tabulate(lease_list, headers) + output = tabulate.tabulate(lease_list, lease_display_fields.values()) print(output) @@ -63,11 +122,14 @@ if __name__ == '__main__': parser = argparse.ArgumentParser() group = parser.add_mutually_exclusive_group() - group.add_argument("-l", "--leases", action="store_true", help="Show DHCP leases") - group.add_argument("-s", "--statistics", action="store_true", help="Show DHCP statistics") + group.add_argument("-l", "--leases", action="store_true", help="Show DHCPv6 leases") + group.add_argument("-s", "--statistics", action="store_true", help="Show DHCPv6 statistics") + group.add_argument("--allowed", type=str, choices=["pool", "sort", "state"], help="Show allowed values for argument") - parser.add_argument("-p", "--pool", type=str, action="store", help="Show lease for specific pool") - parser.add_argument("-j", "--json", action="store_true", default=False, help="Product JSON output") + parser.add_argument("-p", "--pool", type=str, help="Show lease for specific pool") + parser.add_argument("-S", "--sort", type=str, choices=lease_display_fields.keys(), default='ip', help="Sort by") + parser.add_argument("-t", "--state", type=str, choices=lease_valid_states, default="active", help="Lease state to show") + parser.add_argument("-j", "--json", action="store_true", default=False, help="Produce JSON output") args = parser.parse_args() @@ -78,9 +140,19 @@ if __name__ == '__main__': sys.exit(0) if args.leases: - leases = get_leases(lease_file, state='active') - show_leases(leases) + leases = get_leases(lease_file, args.state, args.pool, args.sort) + + if args.json: + print(json.dumps(leases, indent=4)) + else: + show_leases(leases) elif args.statistics: print("DHCPv6 statistics option is not available") + elif args.allowed == 'pool': + print(' '.join(c.list_effective_nodes("service dhcpv6-server shared-network-name"))) + elif args.allowed == 'sort': + print(' '.join(lease_display_fields.keys())) + elif args.allowed == 'state': + print(' '.join(lease_valid_states)) else: - print("Invalid option") + parser.print_help() -- cgit v1.2.3 From 99cca9bea1a23c396b4b3121f759b3e21240fbd0 Mon Sep 17 00:00:00 2001 From: qiuchengxuan Date: Tue, 25 Jun 2019 15:55:55 +0800 Subject: [pdns-recursor] T1469 - specified dns forwarding not work when conflict exists between forward-zone-recurse entry, the lower one hides the upper one, which leads to inactive dns forwarding configuration --- src/conf_mode/dns_forwarding.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 0ce2eee2c..c7e362d07 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -60,16 +60,6 @@ export-etc-hosts={{ export_hosts_file }} # listen-on local-address={{ listen_on | join(',') }} -# domain ... server ... -{% if domains -%} - -forward-zones-recurse={% for d in domains %} -{{ d.name }}={{ d.servers | join(";") }} -{{- "," if not loop.last -}} -{% endfor %} - -{% endif %} - # dnssec dnssec={{ dnssec }} @@ -80,6 +70,16 @@ forward-zones-recurse=.={{ name_servers | join(';') }} # no name-servers specified - start full recursor {% endif %} +# domain ... server ... +{% if domains -%} + +forward-zones-recurse={% for d in domains %} +{{ d.name }}={{ d.servers | join(";") }} +{{- "," if not loop.last -}} +{% endfor %} + +{% endif %} + """ default_config_data = { -- cgit v1.2.3 From 1027d1c622f9d0e6a062fa1119c8bffbf12151fb Mon Sep 17 00:00:00 2001 From: hagbard Date: Mon, 24 Jun 2019 11:03:43 -0700 Subject: [IPoE] T989 - IPoE implementation * chap-secrets file generation * noauth in accel config as option * local auth with csid implemented * radius implementation * shaper per user implemented * op comands for stats --- interface-definitions/ipoe-server.xml | 279 +++++++++++++++++++++++++++++ op-mode-definitions/ipoe-server.xml | 26 +++ src/conf_mode/ipoe_server.py | 325 ++++++++++++++++++++++++++++++++++ 3 files changed, 630 insertions(+) create mode 100644 interface-definitions/ipoe-server.xml create mode 100644 op-mode-definitions/ipoe-server.xml create mode 100755 src/conf_mode/ipoe_server.py (limited to 'src/conf_mode') diff --git a/interface-definitions/ipoe-server.xml b/interface-definitions/ipoe-server.xml new file mode 100644 index 000000000..18968a033 --- /dev/null +++ b/interface-definitions/ipoe-server.xml @@ -0,0 +1,279 @@ + + + + + + + Internet Protocol over Ethernet (IPoE) Server + 900 + + + + + Network interface to server IPoE + + + + + + + + Network Layer IPoE serves on + + L2 L3 + + + ^(L2|L3) + + + L2 + client share the same subnet + + + L3 + clients are behind this router + + + + + + Enables clients to share the same network or each client has its own vlan + + shared vlan + + + ^(shared|vlan) + + + shared + Multiple clients share the same network + + + vlan + One VLAN per client + + + + + + Client address pool + + ipv4net + IPv4 address and prefix length + + + + + + + + + DHCP requests will be forwarded + + + + + DHCP Server the request will be redirected to. + + ipv4 + IPv4 address of the DHCP Server + + + + + + + + + address of the relay agent (Relay Agent IP Address) + + + + + + + + + DNS servers offered via internal DHCP + + + + + IP address of the primary DNS server + + + + + + + + IP address of the primary DNS server + + + + + + + + + + Client authentication methods + + + + + Authetication mode + + local radius noauth + + + ^(local|radius|noauth) + + + local + Authentication based on local definition + + + radius + Authentication based on a RADIUS server + + + noauth + Authentication disabled + + + + + + Network interface the client mac will appear on + + + + + + + + Client mac address allowed to receive an IP address + + h:h:h:h:h:h + Hardware (MAC) address + + + + + + + + + Upload/Download speed limits + + + + + Upload bandwidth limit in kbits/sec + + + + + + + + Download bandwidth limit in kbits/sec + + + + + + + + + + + + + + IP address of RADIUS server + + ipv4 + IP address of RADIUS server + + + + + + Key for accessing the specified server + + + + + Maximum number of simultaneous requests to server (default: unlimited) + + + + + If server doesn't responds mark it as unavailable for this amount of time in seconds + + + + + + + RADIUS settings + + + + + Timeout to wait response from server (seconds) + + + + + Timeout to wait reply for Interim-Update packets. (default 3 seconds) + + + + + Maximum number of tries to send Access-Request/Accounting-Request queries + + + + + Value to send to RADIUS server in NAS-Identifier attribute and to be matched in DM/CoA requests. + + + + + Value to send to RADIUS server in NAS-IP-Address attribute and to be matched in DM/CoA requests. Also DM/CoA server will bind to that address. + + + + + IPv4 address and port to bind Dynamic Authorization Extension server (DM/CoA) + + + + + IP address for Dynamic Authorization Extension server (DM/CoA) + + + + + Port for Dynamic Authorization Extension server (DM/CoA) + + + + + Secret for Dynamic Authorization Extension server (DM/CoA) + + + + + + + + + + + + + diff --git a/op-mode-definitions/ipoe-server.xml b/op-mode-definitions/ipoe-server.xml new file mode 100644 index 000000000..484201f40 --- /dev/null +++ b/op-mode-definitions/ipoe-server.xml @@ -0,0 +1,26 @@ + + + + + + + show ipoe-server status + + + + + Show active IPoE server sessions + + /usr/bin/accel-cmd '-p 2002 show sessions ifname,called-sid,calling-sid,ip,ip6,ip6-dp,rate-limit,state,uptime,sid' + + + + Show IPoE server statistics + + /usr/bin/accel-cmd '-p 2002 show stat' + + + + + + diff --git a/src/conf_mode/ipoe_server.py b/src/conf_mode/ipoe_server.py new file mode 100755 index 000000000..39f0cb279 --- /dev/null +++ b/src/conf_mode/ipoe_server.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 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 os +import re +import time +import socket +import subprocess +import jinja2 +import syslog as sl + +from vyos.config import Config +from vyos import ConfigError + +ipoe_cnf_dir = r'/etc/accel-ppp/ipoe' +ipoe_cnf = ipoe_cnf_dir + r'/ipoe.config' + +pidfile = r'/var/run/accel_ipoe.pid' +cmd_port = r'2002' + +chap_secrets = ipoe_cnf_dir + '/chap-secrets' +## accel-pppd -d -c /etc/accel-ppp/pppoe/pppoe.config -p /var/run/accel_pppoe.pid + +ipoe_config = ''' +### generated by ipoe.py ### +[modules] +log_syslog +ippool +ipoe +shaper +{% if auth == 'radius' %} +radius +{% endif -%} +{% if auth == 'local' %} +chap-secrets +{% endif %} + +[core] +thread-count={{thread_cnt}} + +[log] +syslog=accel-ipoe,daemon +copy=1 +level=5 + +[ipoe] +verbose=1 +{% for intfc in interfaces %} +interface={{intfc}},\ +shared={{interfaces[intfc]['shared']}},\ +mode={{interfaces[intfc]['mode']}},\ +ifcfg={{interfaces[intfc]['ifcfg']}},\ +range={{interfaces[intfc]['range']}},\ +start={{interfaces[intfc]['sess_start']}} +{% endfor %} +{% if auth == 'noauth' %} +noauth=1 +{% endif %} +{% if auth == 'local' %} +username=ifname +password=csid +{% endif %} + +{% if (dns['server1']) or (dns['server2']) %} +[dns] +{% if dns['server1'] %} +dns1={{dns['server1']}} +{% endif -%} +{% if dns['server2'] %} +dns2={{dns['server2']}} +{% endif -%} +{% endif %} + +{% if auth == 'local' %} +[chap-secrets] +chap-secrets=/etc/accel-ppp/ipoe/chap-secrets +{% endif %} + +{% if auth == 'radius' %} +[radius] +verbose=1 +{% for srv in radius %} +server={{srv}},{{radius[srv]['secret']}},\ +req-limit={{radius[srv]['req-limit']}},\ +fail-time={{radius[srv]['fail-time']}} +{% endfor %} +{% if radsettings['dae-server']['ip-address'] %} +dae-server={{radsettings['dae-server']['ip-address']}}:{{radsettings['dae-server']['port']}},{{radsettings['dae-server']['secret']}} +{% endif -%} +{% if radsettings['acct-timeout'] %} +acct-timeout={{radsettings['acct-timeout']}} +{% endif -%} +{% if radsettings['max-try'] %} +max-try={{radsettings['max-try']}} +{% endif -%} +{% if radsettings['nas-ip-address'] %} +nas-ip-address={{radsettings['nas-ip-address']}} +{% endif -%} +{% if radsettings['nas-identifier'] %} +nas-identifier={{radsettings['nas-identifier']}} +{% endif -%} +{% endif %} + +[cli] +tcp=127.0.0.1:2002 +''' + +### pppoe chap secrets +chap_secrets_conf = ''' +# username server password acceptable local IP addresses shaper +{% for aifc in auth_if %} +{% for mac in auth_if[aifc] %} +{% if (auth_if[aifc][mac]['up']) and (auth_if[aifc][mac]['down']) %} +{{aifc}}\t*\t{{mac}}\t*\t{{auth_if[aifc][mac]['down']}}/{{auth_if[aifc][mac]['up']}} +{% else %} +{{aifc}}\t*\t{{mac}}\t* +{% endif %} +{% endfor %} +{% endfor %} +''' + +##### Inline functions start #### +### config path creation +if not os.path.exists(ipoe_cnf_dir): + os.makedirs(ipoe_cnf_dir) + sl.syslog(sl.LOG_NOTICE, ipoe_cnf_dir + " created") + +def get_cpu(): + cpu_cnt = 1 + if os.cpu_count() == 1: + cpu_cnt = 1 + else: + cpu_cnt = int(os.cpu_count()/2) + return cpu_cnt + +def chk_con(): + cnt = 0 + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + while True: + try: + s.connect(("127.0.0.1", int(cmd_port))) + break + except ConnectionRefusedError: + time.sleep(0.5) + cnt +=1 + if cnt == 100: + raise("failed to start pppoe server") + break + +def accel_cmd(cmd=''): + if not cmd: + return None + try: + ret = subprocess.check_output(['/usr/bin/accel-cmd', '-p', cmd_port, cmd]).decode().strip() + return ret + except: + return 1 + +### chap_secrets file if auth mode local +def gen_chap_secrets(c): + tmpl = jinja2.Template(chap_secrets_conf, trim_blocks=True) + chap_secrets_txt = tmpl.render(c) + old_umask = os.umask(0o077) + open(chap_secrets,'w').write(chap_secrets_txt) + os.umask(old_umask) + sl.syslog(sl.LOG_NOTICE, chap_secrets + ' written') + +##### Inline functions end #### + +def get_config(): + c = Config() + if not c.exists('service ipoe-server'): + return None + + config_data = {} + + c.set_level('service ipoe-server') + for intfc in c.list_nodes('interface'): + config_data.update( + { + 'interfaces' : { + intfc : { + 'mode' : 'L2', + 'shared' : '1', + 'sess_start' : 'dhcpv4', ### may need a conifg option, can be dhcpv4 or up for unclassified pkts + 'range' : '', + 'ifcfg' : '1' + } + }, + 'dns' : { + 'server1' : None, + 'server2' : None + }, + 'auth' : 'noauth', + 'auth_if' : {}, + 'radius' : {}, + 'radsettings' : { + 'dae-server' : {} + } + } + ) + + if c.exists('interface ' + intfc + ' network-mode'): + config_data['interfaces'][intfc]['mode'] = c.return_value('interface ' + intfc + ' network-mode') + if c.return_value('interface ' + intfc + ' network') == 'vlan': + config_data['interfaces'][intfc]['shared'] = '0' + if c.exists('interface ' + intfc + ' client-subnet'): + config_data['interfaces'][intfc]['range'] = c.return_value('interface ' + intfc + ' client-subnet') + if c.exists('dns-server server-1'): + config_data['dns']['server1'] = c.return_value('dns-server server-1') + if c.exists('dns-server server-2'): + config_data['dns']['server2'] = c.return_value('dns-server server-2') + if not c.exists('authentication mode noauth'): + config_data['auth'] = c.return_value('authentication mode') + if c.exists('authentication mode local'): + for auth_int in c.list_nodes('authentication interface'): + for mac in c.list_nodes('authentication interface ' + auth_int + ' mac-address'): + config_data['auth_if'][auth_int] = {} + if c.exists('authentication interface ' + auth_int + ' mac-address ' + mac + ' rate-limit'): + config_data['auth_if'][auth_int][mac] = {} + config_data['auth_if'][auth_int][mac]['up'] = c.return_value('authentication interface ' + auth_int + ' mac-address ' + mac + ' rate-limit upload') + config_data['auth_if'][auth_int][mac]['down'] = c.return_value('authentication interface ' + auth_int + ' mac-address ' + mac + ' rate-limit download') + else: + config_data['auth_if'][auth_int][mac] = {} + config_data['auth_if'][auth_int][mac]['up'] = None + config_data['auth_if'][auth_int][mac]['down'] = None + if c.exists('authentication mode radius'): + for rsrv in c.list_nodes('authentication radius-server'): + config_data['radius'][rsrv] = {} + if c.exists('authentication radius-server ' + rsrv + ' secret'): + config_data['radius'][rsrv]['secret'] = c.return_value('authentication radius-server ' + rsrv + ' secret') + if c.exists('authentication radius-server ' + rsrv + ' fail-time'): + config_data['radius'][rsrv]['fail-time'] = c.return_value('authentication radius-server ' + rsrv + ' fail-time') + else: + config_data['radius'][rsrv]['fail-time'] = '0' + if c.exists('authentication radius-server ' + rsrv + ' req-limit'): + config_data['radius'][rsrv]['req-limit'] = c.return_value('authentication radius-server ' + rsrv + ' req-limit') + else: + config_data['radius'][rsrv]['req-limit'] = '0' + if c.exists('authentication radius-settings'): + if c.exists('authentication radius-settings timeout'): + config_data['radsettings']['timeout'] = c.return_value('authentication radius-settings timeout') + if c.exists('authentication radius-settings nas-ip-address'): + config_data['radsettings']['nas-ip-address'] = c.return_value('authentication radius-settings nas-ip-address') + if c.exists('authentication radius-settings nas-identifier'): + config_data['radsettings']['nas-identifier'] = c.return_value('authentication radius-settings nas-identifier') + if c.exists('authentication radius-settings max-try'): + config_data['radsettings']['max-try'] = c.return_value('authentication radius-settings max-try') + if c.exists('authentication radius-settings acct-timeout'): + config_data['radsettings']['acct-timeout'] = c.return_value('authentication radius-settings acct-timeout') + if c.exists('authentication radius-settings dae-server ip-address'): + config_data['radsettings']['dae-server']['ip-address'] = c.return_value('authentication radius-settings dae-server ip-address') + if c.exists('authentication radius-settings dae-server port'): + config_data['radsettings']['dae-server']['port'] = c.return_value('authentication radius-settings dae-server port') + if c.exists('authentication radius-settings dae-server secret'): + config_data['radsettings']['dae-server']['secret'] = c.return_value('authentication radius-settings dae-server secret') + + return config_data + +def generate(c): + if c == None or not c: + return None + + c['thread_cnt'] = get_cpu() + + if c['auth'] == 'local': + gen_chap_secrets(c) + + tmpl = jinja2.Template(ipoe_config, trim_blocks=True) + config_text = tmpl.render(c) + + open(ipoe_cnf,'w').write(config_text) + return c + +def verify(c): + if c == None or not c: + return None + + for intfc in c['interfaces']: + if not c['interfaces'][intfc]['range']: + raise ConfigError("service ipoe-server interface eth2 client-subnet needs a value") + +def apply(c): + if c == None: + if os.path.exists(pidfile): + accel_cmd('shutdown hard') + if os.path.exists(pidfile): + os.remove(pidfile) + return None + + if not os.path.exists(pidfile): + ret = subprocess.call(['/usr/sbin/accel-pppd', '-c', ipoe_cnf, '-p', pidfile, '-d']) + chk_con() + if ret !=0 and os.path.exists(pidfile): + os.remove(pidfile) + raise ConfigError('accel-pppd failed to start') + else: + accel_cmd('restart') + sl.syslog(sl.LOG_NOTICE, "reloading config via daemon restart") + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) -- cgit v1.2.3 From b23c28dfd0e296987d7f65c2a178e6ed0fde3983 Mon Sep 17 00:00:00 2001 From: hagbard Date: Fri, 28 Jun 2019 09:54:25 -0700 Subject: [IPoE] if authentication is local use .lower() for mac addresses --- src/conf_mode/ipoe_server.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/ipoe_server.py b/src/conf_mode/ipoe_server.py index 39f0cb279..470ef7ee6 100755 --- a/src/conf_mode/ipoe_server.py +++ b/src/conf_mode/ipoe_server.py @@ -231,6 +231,7 @@ def get_config(): if c.exists('authentication mode local'): for auth_int in c.list_nodes('authentication interface'): for mac in c.list_nodes('authentication interface ' + auth_int + ' mac-address'): + mac = mac.lower() config_data['auth_if'][auth_int] = {} if c.exists('authentication interface ' + auth_int + ' mac-address ' + mac + ' rate-limit'): config_data['auth_if'][auth_int][mac] = {} -- cgit v1.2.3 From 2df12d1616c3f63c5db5a76ab315d06fa7d5d190 Mon Sep 17 00:00:00 2001 From: hagbard Date: Fri, 28 Jun 2019 10:20:06 -0700 Subject: [IPoE] configerror message fixed to show the interface where subnet is missing --- src/conf_mode/ipoe_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/ipoe_server.py b/src/conf_mode/ipoe_server.py index 470ef7ee6..4ecff2e8c 100755 --- a/src/conf_mode/ipoe_server.py +++ b/src/conf_mode/ipoe_server.py @@ -295,7 +295,7 @@ def verify(c): for intfc in c['interfaces']: if not c['interfaces'][intfc]['range']: - raise ConfigError("service ipoe-server interface eth2 client-subnet needs a value") + raise ConfigError("service ipoe-server interface " + intfc + " client-subnet needs a value") def apply(c): if c == None: -- cgit v1.2.3 From b83fed095c418d27a08309af2d6bf50c11505117 Mon Sep 17 00:00:00 2001 From: hagbard Date: Fri, 28 Jun 2019 12:39:40 -0700 Subject: [IPoE] config structure improved * fixed minor issues * fixed lower function for mac addresses if user capitalized it (local mode only) * added some checks to verify() * cli ip-address checks on input --- interface-definitions/ipoe-server.xml | 21 ++++ src/conf_mode/ipoe_server.py | 177 ++++++++++++++++++++-------------- 2 files changed, 125 insertions(+), 73 deletions(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/ipoe-server.xml b/interface-definitions/ipoe-server.xml index 18968a033..4884b5915 100644 --- a/interface-definitions/ipoe-server.xml +++ b/interface-definitions/ipoe-server.xml @@ -244,6 +244,13 @@ Value to send to RADIUS server in NAS-IP-Address attribute and to be matched in DM/CoA requests. Also DM/CoA server will bind to that address. + + ipv4 + IPv4 address of the DAE Server + + + + @@ -254,11 +261,25 @@ IP address for Dynamic Authorization Extension server (DM/CoA) + + ipv4 + IPv4 address of the DAE Server + + + + Port for Dynamic Authorization Extension server (DM/CoA) + + number + port number + + + + diff --git a/src/conf_mode/ipoe_server.py b/src/conf_mode/ipoe_server.py index 4ecff2e8c..478fc139e 100755 --- a/src/conf_mode/ipoe_server.py +++ b/src/conf_mode/ipoe_server.py @@ -44,10 +44,10 @@ log_syslog ippool ipoe shaper -{% if auth == 'radius' %} +{% if auth['mech'] == 'radius' %} radius {% endif -%} -{% if auth == 'local' %} +{% if auth['mech'] == 'local' %} chap-secrets {% endif %} @@ -69,10 +69,10 @@ ifcfg={{interfaces[intfc]['ifcfg']}},\ range={{interfaces[intfc]['range']}},\ start={{interfaces[intfc]['sess_start']}} {% endfor %} -{% if auth == 'noauth' %} +{% if auth['mech'] == 'noauth' %} noauth=1 {% endif %} -{% if auth == 'local' %} +{% if auth['mech'] == 'local' %} username=ifname password=csid {% endif %} @@ -85,35 +85,40 @@ dns1={{dns['server1']}} {% if dns['server2'] %} dns2={{dns['server2']}} {% endif -%} -{% endif %} +{% endif -%} -{% if auth == 'local' %} +{% if auth['mech'] == 'local' %} [chap-secrets] chap-secrets=/etc/accel-ppp/ipoe/chap-secrets {% endif %} -{% if auth == 'radius' %} +{% if auth['mech'] == 'radius' %} [radius] verbose=1 -{% for srv in radius %} -server={{srv}},{{radius[srv]['secret']}},\ -req-limit={{radius[srv]['req-limit']}},\ -fail-time={{radius[srv]['fail-time']}} +{% for srv in auth['radius'] %} +server={{srv}},{{auth['radius'][srv]['secret']}},\ +req-limit={{auth['radius'][srv]['req-limit']}},\ +fail-time={{auth['radius'][srv]['fail-time']}} {% endfor %} -{% if radsettings['dae-server']['ip-address'] %} -dae-server={{radsettings['dae-server']['ip-address']}}:{{radsettings['dae-server']['port']}},{{radsettings['dae-server']['secret']}} +{% if auth['radsettings']['dae-server']['ip-address'] %} +dae-server={{auth['radsettings']['dae-server']['ip-address']}}:\ +{{auth['radsettings']['dae-server']['port']}},\ +{{auth['radsettings']['dae-server']['secret']}} +{% endif -%} +{% if auth['radsettings']['acct-timeout'] %} +acct-timeout={{auth['radsettings']['acct-timeout']}} {% endif -%} -{% if radsettings['acct-timeout'] %} -acct-timeout={{radsettings['acct-timeout']}} +{% if auth['radsettings']['max-try'] %} +max-try={{auth['radsettings']['max-try']}} {% endif -%} -{% if radsettings['max-try'] %} -max-try={{radsettings['max-try']}} +{% if auth['radsettings']['timeout'] %} +timeout={{auth['radsettings']['timeout']}} {% endif -%} -{% if radsettings['nas-ip-address'] %} -nas-ip-address={{radsettings['nas-ip-address']}} +{% if auth['radsettings']['nas-ip-address'] %} +nas-ip-address={{auth['radsettings']['nas-ip-address']}} {% endif -%} -{% if radsettings['nas-identifier'] %} -nas-identifier={{radsettings['nas-identifier']}} +{% if auth['radsettings']['nas-identifier'] %} +nas-identifier={{auth['radsettings']['nas-identifier']}} {% endif -%} {% endif %} @@ -124,12 +129,12 @@ tcp=127.0.0.1:2002 ### pppoe chap secrets chap_secrets_conf = ''' # username server password acceptable local IP addresses shaper -{% for aifc in auth_if %} -{% for mac in auth_if[aifc] %} -{% if (auth_if[aifc][mac]['up']) and (auth_if[aifc][mac]['down']) %} -{{aifc}}\t*\t{{mac}}\t*\t{{auth_if[aifc][mac]['down']}}/{{auth_if[aifc][mac]['up']}} +{% for aifc in auth['auth_if'] %} +{% for mac in auth['auth_if'][aifc] %} +{% if (auth['auth_if'][aifc][mac]['up']) and (auth['auth_if'][aifc][mac]['down']) %} +{{aifc}}\t*\t{{mac.lower()}}\t*\t{{auth['auth_if'][aifc][mac]['down']}}/{{auth['auth_if'][aifc][mac]['up']}} {% else %} -{{aifc}}\t*\t{{mac}}\t* +{{aifc}}\t*\t{{mac.lower()}}\t* {% endif %} {% endfor %} {% endfor %} @@ -191,30 +196,27 @@ def get_config(): config_data = {} c.set_level('service ipoe-server') + config_data['interfaces'] = {} for intfc in c.list_nodes('interface'): - config_data.update( - { - 'interfaces' : { - intfc : { - 'mode' : 'L2', - 'shared' : '1', - 'sess_start' : 'dhcpv4', ### may need a conifg option, can be dhcpv4 or up for unclassified pkts - 'range' : '', - 'ifcfg' : '1' - } - }, - 'dns' : { - 'server1' : None, - 'server2' : None - }, - 'auth' : 'noauth', - 'auth_if' : {}, - 'radius' : {}, - 'radsettings' : { - 'dae-server' : {} - } + config_data['interfaces'][intfc] = { + 'mode' : 'L2', + 'shared' : '1', + 'sess_start' : 'dhcpv4', ### may need a conifg option, can be dhcpv4 or up for unclassified pkts + 'range' : None, + 'ifcfg' : '1' + } + config_data['dns'] = { + 'server1' : None, + 'server2' : None + } + config_data['auth'] = { + 'auth_if' : {}, + 'mech' : 'noauth', + 'radius' : {}, + 'radsettings' : { + 'dae-server' : {} } - ) + } if c.exists('interface ' + intfc + ' network-mode'): config_data['interfaces'][intfc]['mode'] = c.return_value('interface ' + intfc + ' network-mode') @@ -227,50 +229,51 @@ def get_config(): if c.exists('dns-server server-2'): config_data['dns']['server2'] = c.return_value('dns-server server-2') if not c.exists('authentication mode noauth'): - config_data['auth'] = c.return_value('authentication mode') + config_data['auth']['mech'] = c.return_value('authentication mode') if c.exists('authentication mode local'): for auth_int in c.list_nodes('authentication interface'): for mac in c.list_nodes('authentication interface ' + auth_int + ' mac-address'): - mac = mac.lower() - config_data['auth_if'][auth_int] = {} + config_data['auth']['auth_if'][auth_int] = {} if c.exists('authentication interface ' + auth_int + ' mac-address ' + mac + ' rate-limit'): - config_data['auth_if'][auth_int][mac] = {} - config_data['auth_if'][auth_int][mac]['up'] = c.return_value('authentication interface ' + auth_int + ' mac-address ' + mac + ' rate-limit upload') - config_data['auth_if'][auth_int][mac]['down'] = c.return_value('authentication interface ' + auth_int + ' mac-address ' + mac + ' rate-limit download') + config_data['auth']['auth_if'][auth_int][mac] = {} + config_data['auth']['auth_if'][auth_int][mac]['up'] = c.return_value('authentication interface ' + auth_int + ' mac-address ' + mac + ' rate-limit upload') + config_data['auth']['auth_if'][auth_int][mac]['down'] = c.return_value('authentication interface ' + auth_int + ' mac-address ' + mac + ' rate-limit download') else: - config_data['auth_if'][auth_int][mac] = {} - config_data['auth_if'][auth_int][mac]['up'] = None - config_data['auth_if'][auth_int][mac]['down'] = None + config_data['auth']['auth_if'][auth_int][mac] = {} + config_data['auth']['auth_if'][auth_int][mac]['up'] = None + config_data['auth']['auth_if'][auth_int][mac]['down'] = None if c.exists('authentication mode radius'): for rsrv in c.list_nodes('authentication radius-server'): - config_data['radius'][rsrv] = {} + config_data['auth']['radius'][rsrv] = {} if c.exists('authentication radius-server ' + rsrv + ' secret'): - config_data['radius'][rsrv]['secret'] = c.return_value('authentication radius-server ' + rsrv + ' secret') + config_data['auth']['radius'][rsrv]['secret'] = c.return_value('authentication radius-server ' + rsrv + ' secret') + else: + config_data['auth']['radius'][rsrv]['secret'] = None if c.exists('authentication radius-server ' + rsrv + ' fail-time'): - config_data['radius'][rsrv]['fail-time'] = c.return_value('authentication radius-server ' + rsrv + ' fail-time') + config_data['auth']['radius'][rsrv]['fail-time'] = c.return_value('authentication radius-server ' + rsrv + ' fail-time') else: - config_data['radius'][rsrv]['fail-time'] = '0' + config_data['auth']['radius'][rsrv]['fail-time'] = '0' if c.exists('authentication radius-server ' + rsrv + ' req-limit'): - config_data['radius'][rsrv]['req-limit'] = c.return_value('authentication radius-server ' + rsrv + ' req-limit') + config_data['auth']['radius'][rsrv]['req-limit'] = c.return_value('authentication radius-server ' + rsrv + ' req-limit') else: - config_data['radius'][rsrv]['req-limit'] = '0' + config_data['auth']['radius'][rsrv]['req-limit'] = '0' if c.exists('authentication radius-settings'): if c.exists('authentication radius-settings timeout'): - config_data['radsettings']['timeout'] = c.return_value('authentication radius-settings timeout') + config_data['auth']['radsettings']['timeout'] = c.return_value('authentication radius-settings timeout') if c.exists('authentication radius-settings nas-ip-address'): - config_data['radsettings']['nas-ip-address'] = c.return_value('authentication radius-settings nas-ip-address') + config_data['auth']['radsettings']['nas-ip-address'] = c.return_value('authentication radius-settings nas-ip-address') if c.exists('authentication radius-settings nas-identifier'): - config_data['radsettings']['nas-identifier'] = c.return_value('authentication radius-settings nas-identifier') + config_data['auth']['radsettings']['nas-identifier'] = c.return_value('authentication radius-settings nas-identifier') if c.exists('authentication radius-settings max-try'): - config_data['radsettings']['max-try'] = c.return_value('authentication radius-settings max-try') + config_data['auth']['radsettings']['max-try'] = c.return_value('authentication radius-settings max-try') if c.exists('authentication radius-settings acct-timeout'): - config_data['radsettings']['acct-timeout'] = c.return_value('authentication radius-settings acct-timeout') + config_data['auth']['radsettings']['acct-timeout'] = c.return_value('authentication radius-settings acct-timeout') if c.exists('authentication radius-settings dae-server ip-address'): - config_data['radsettings']['dae-server']['ip-address'] = c.return_value('authentication radius-settings dae-server ip-address') + config_data['auth']['radsettings']['dae-server']['ip-address'] = c.return_value('authentication radius-settings dae-server ip-address') if c.exists('authentication radius-settings dae-server port'): - config_data['radsettings']['dae-server']['port'] = c.return_value('authentication radius-settings dae-server port') + config_data['auth']['radsettings']['dae-server']['port'] = c.return_value('authentication radius-settings dae-server port') if c.exists('authentication radius-settings dae-server secret'): - config_data['radsettings']['dae-server']['secret'] = c.return_value('authentication radius-settings dae-server secret') + config_data['auth']['radsettings']['dae-server']['secret'] = c.return_value('authentication radius-settings dae-server secret') return config_data @@ -280,7 +283,7 @@ def generate(c): c['thread_cnt'] = get_cpu() - if c['auth'] == 'local': + if c['auth']['mech'] == 'local': gen_chap_secrets(c) tmpl = jinja2.Template(ipoe_config, trim_blocks=True) @@ -295,7 +298,35 @@ def verify(c): for intfc in c['interfaces']: if not c['interfaces'][intfc]['range']: - raise ConfigError("service ipoe-server interface " + intfc + " client-subnet needs a value") + raise ConfigError("service ipoe-server interface " + intfc + " client-subnet needs a value") + + if c['auth']['mech'] == 'radius': + if not c['auth']['radius']: + raise ConfigError("service ipoe-server authentication radius-server requires a value for authentication mode radius") + else: + for radsrv in c['auth']['radius']: + if not c['auth']['radius'][radsrv]['secret']: + raise ConfigError("service ipoe-server authentication radius-server " + radsrv + " secret requires a value") + + if c['auth']['radsettings']['dae-server']: + try: + if c['auth']['radsettings']['dae-server']['ip-address']: + pass + except: + raise ConfigError("service ipoe-server authentication radius-settings dae-server ip-address value required") + try: + if c['auth']['radsettings']['dae-server']['secret']: + pass + except: + raise ConfigError("service ipoe-server authentication radius-settings dae-server secret value required") + try: + if c['auth']['radsettings']['dae-server']['port']: + pass + except: + raise ConfigError("service ipoe-server authentication radius-settings dae-server port value required") + + + return c def apply(c): if c == None: -- cgit v1.2.3 From d56d52990f5b30a1b03b2479767e91aa3aa2cdc5 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 1 Jul 2019 13:57:52 -0500 Subject: [service https] T1443: add service https and service https api --- debian/rules | 3 + interface-definitions/https.xml | 87 ++++++++++++++++++++++++++ python/vyos/defaults.py | 1 + src/conf_mode/http-api.py | 104 +++++++++++++++++++++++++++++++ src/conf_mode/https.py | 132 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 327 insertions(+) create mode 100644 interface-definitions/https.xml create mode 100755 src/conf_mode/http-api.py create mode 100755 src/conf_mode/https.py (limited to 'src/conf_mode') diff --git a/debian/rules b/debian/rules index b06117922..952867a76 100755 --- a/debian/rules +++ b/debian/rules @@ -77,3 +77,6 @@ override_dh_auto_install: # Install systemd service units mkdir -p $(DIR)/lib/systemd/system cp -r src/systemd/* $(DIR)/lib/systemd/system + + # Make directory for generated configuration file + mkdir -p $(DIR)/etc/vyos diff --git a/interface-definitions/https.xml b/interface-definitions/https.xml new file mode 100644 index 000000000..828de449c --- /dev/null +++ b/interface-definitions/https.xml @@ -0,0 +1,87 @@ + + + + + + + + HTTPS configuration + 1001 + + + + + Addresses to listen for HTTPS requests + + ipv4 + HTTPS IPv4 address + + + ipv6 + HTTPS IPv6 address + + + + + + + + + + + VyOS HTTP API configuration + 1002 + + + + + Port for HTTP API service + + 1-65535 + Numeric IP port + + + + + + + + + HTTP API keys + + + + + HTTP API id + + + + + HTTP API plaintext key + + + + + + + + + Enforce strict path checking + + + + + + Debug + + + + + + + + + + + + diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index da363b8e1..f23e15631 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -16,6 +16,7 @@ directories = { "data": "/usr/share/vyos/", + "conf_mode": "/usr/libexec/vyos/conf_mode", "config": "/opt/vyatta/etc/config", "current": "/opt/vyatta/etc/config-migrate/current", "migrate": "/opt/vyatta/etc/config-migrate/migrate", diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py new file mode 100755 index 000000000..7d618dded --- /dev/null +++ b/src/conf_mode/http-api.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# + +import sys +import os +import subprocess +import json + +import vyos.defaults +from vyos.config import Config +from vyos import ConfigError + +config_file = '/etc/vyos/http-api.conf' + +default_config_data = { + 'listen_address' : '127.0.0.1', + 'port' : '8080', + 'strict' : 'false', + 'debug' : 'false', + 'api_keys' : [ {"id": "testapp", "key": "qwerty"} ] +} + +vyos_conf_scripts_dir=vyos.defaults.directories['conf_mode'] + +# XXX: this model will need to be extended for tag nodes +dependencies = [ + 'https.py', +] + +def get_config(): + http_api = default_config_data + conf = Config() + if not conf.exists('service https api'): + return None + else: + conf.set_level('service https api') + + if conf.exists('strict'): + http_api['strict'] = 'true' + + if conf.exists('debug'): + http_api['debug'] = 'true' + + if conf.exists('port'): + port = conf.return_value('port') + http_api['port'] = port + + if conf.exists('keys'): + for name in conf.list_nodes('keys id'): + if conf.exists('keys id {0} key'.format(name)): + key = conf.return_value('keys id {0} key'.format(name)) + new_key = { 'id': name, 'key': key } + http_api['api_keys'].append(new_key) + + return http_api + +def verify(http_api): + return None + +def generate(http_api): + if http_api is None: + return None + + with open(config_file, 'w') as f: + json.dump(http_api, f, indent=2) + + return None + +def apply(http_api): + if http_api is not None: + os.system('sudo systemctl restart vyos-http-api.service') + for dep in dependencies: + cmd = '{0}/{1}'.format(vyos_conf_scripts_dir, dep) + try: + subprocess.check_call(cmd, shell=True) + except subprocess.CalledProcessError as err: + raise ConfigError("{}.".format(err)) + else: + os.system('sudo systemctl stop vyos-http-api.service') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py new file mode 100755 index 000000000..dae51dd7d --- /dev/null +++ b/src/conf_mode/https.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# + +import sys +import os + +import jinja2 + +from vyos.config import Config +from vyos import ConfigError + +config_file = '/etc/nginx/sites-available/default' + +# Please be careful if you edit the template. +config_tmpl = """ + +### Autogenerated by http-api.py ### +# Default server configuration +# +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + return 302 https://$server_name$request_uri; +} + +server { + + # SSL configuration + # + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + # + # Self signed certs generated by the ssl-cert package + # Don't use them in a production server! + # + include snippets/snakeoil.conf; + +{% for l_addr in listen_address %} + server_name {{ l_addr }}; +{% endfor %} + + location / { +{% if api %} + proxy_pass http://localhost:{{ api.port }}; + proxy_buffering off; +{% endif %} + } + + error_page 501 502 503 =200 @50*_json; + + location @50*_json { + default_type application/json; + return 200 '{"error": "Start service in configuration mode: set service https api"}'; + } + +} +""" + +default_config_data = { + 'listen_address' : [ '127.0.0.1' ] +} + +default_api_config_data = { + 'port' : '8080', +} + +def get_config(): + https = default_config_data + conf = Config() + if not conf.exists('service https'): + return None + else: + conf.set_level('service https') + + if conf.exists('listen-address'): + addrs = conf.return_values('listen-address') + https['listen_address'] = addrs[:] + + if conf.exists('api'): + https['api'] = default_api_config_data + + if conf.exists('api port'): + port = conf.return_value('api port') + https['api']['port'] = port + + return https + +def verify(https): + return None + +def generate(https): + if https is None: + return None + + tmpl = jinja2.Template(config_tmpl, trim_blocks=True) + config_text = tmpl.render(https) + with open(config_file, 'w') as f: + f.write(config_text) + + return None + +def apply(https): + if https is not None: + os.system('sudo systemctl restart nginx.service') + else: + os.system('sudo systemctl stop nginx.service') + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + sys.exit(1) -- cgit v1.2.3 From 6c2b62eafa0e2224fdc492c55d255228593ad960 Mon Sep 17 00:00:00 2001 From: UnicronNL Date: Tue, 2 Jul 2019 20:49:06 +0200 Subject: T1497: "set system name-server" generates invalid/incorrect resolv.conf --- src/conf_mode/host_name.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index b0a4648c7..43f36dd35 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -124,7 +124,10 @@ def get_config(arguments): hosts['domain_search'].append(search) if conf.exists("system name-server"): - hosts['nameserver'] = conf.return_values("system name-server") + if not isinstance(conf.return_values("system name-server"), list): + hosts['nameserver'] = conf.return_values("system name-server").replace("'", "").split() + else: + hosts['nameserver'] = conf.return_values("system name-server") if conf.exists("system disable-dhcp-nameservers"): hosts['no_dhcp_ns'] = conf.exists('system disable-dhcp-nameservers') -- cgit v1.2.3 From c35d1b7a1d958327f67c806740428929ff86b151 Mon Sep 17 00:00:00 2001 From: hagbard Date: Tue, 2 Jul 2019 13:09:11 -0700 Subject: [IPoE] T1495 - IA-PD via IPoE implemented --- interface-definitions/ipoe-server.xml | 52 ++++++++++++++++++++++++++++++++++- src/conf_mode/ipoe_server.py | 52 +++++++++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 3 deletions(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/ipoe-server.xml b/interface-definitions/ipoe-server.xml index 4884b5915..46ac2357a 100644 --- a/interface-definitions/ipoe-server.xml +++ b/interface-definitions/ipoe-server.xml @@ -107,7 +107,7 @@ - IP address of the primary DNS server + IP address of the secondary DNS server @@ -115,6 +115,56 @@ + + + DNSv6 servers offered via internal DHCPv6 + + + + + IP address of the primary DNS server + + + + + + + + IP address of the secondary DNS server + + + + + + + + IP address of the tertiary DNS server + + + + + + + + + + Pool of client IPv6 addresses + + + + + Format: ipv6prefix/mask,prefix_len (e.g.: fc00:0:1::/48,64 - divides prefix into /64 subnets for clients) + + + + + + Format: ipv6prefix/mask,prefix_len (delegates prefix to clients via DHCPv6 prefix delegation + + + + + Client authentication methods diff --git a/src/conf_mode/ipoe_server.py b/src/conf_mode/ipoe_server.py index 478fc139e..45c64c617 100755 --- a/src/conf_mode/ipoe_server.py +++ b/src/conf_mode/ipoe_server.py @@ -44,6 +44,9 @@ log_syslog ippool ipoe shaper +ipv6pool +ipv6_nd +ipv6_dhcp {% if auth['mech'] == 'radius' %} radius {% endif -%} @@ -67,7 +70,8 @@ shared={{interfaces[intfc]['shared']}},\ mode={{interfaces[intfc]['mode']}},\ ifcfg={{interfaces[intfc]['ifcfg']}},\ range={{interfaces[intfc]['range']}},\ -start={{interfaces[intfc]['sess_start']}} +start={{interfaces[intfc]['sess_start']}},\ +ipv6=1 {% endfor %} {% if auth['mech'] == 'noauth' %} noauth=1 @@ -87,6 +91,29 @@ dns2={{dns['server2']}} {% endif -%} {% endif -%} +{% if (dnsv6['server1']) or (dnsv6['server2']) or (dnsv6['server3']) %} +[dnsv6] +dns={{dnsv6['server1']}} +dns={{dnsv6['server2']}} +dns={{dnsv6['server3']}} +{% endif %} + +[ipv6-nd] +verbose=1 + +[ipv6-dhcp] +verbose=1 + +{% if ipv6['prfx'] %} +[ipv6-pool] +{% for prfx in ipv6['prfx'] %} +{{prfx}} +{% endfor %} +{% for pd in ipv6['pd'] %} +delegate={{pd}} +{% endfor %} +{% endif %} + {% if auth['mech'] == 'local' %} [chap-secrets] chap-secrets=/etc/accel-ppp/ipoe/chap-secrets @@ -209,6 +236,15 @@ def get_config(): 'server1' : None, 'server2' : None } + config_data['dnsv6'] = { + 'server1' : None, + 'server2' : None, + 'server3' : None + } + config_data['ipv6'] = { + 'prfx' : [], + 'pd' : [], + } config_data['auth'] = { 'auth_if' : {}, 'mech' : 'noauth', @@ -228,6 +264,12 @@ def get_config(): config_data['dns']['server1'] = c.return_value('dns-server server-1') if c.exists('dns-server server-2'): config_data['dns']['server2'] = c.return_value('dns-server server-2') + if c.exists('dnsv6-server server-1'): + config_data['dnsv6']['server1'] = c.return_value('dnsv6-server server-1') + if c.exists('dnsv6-server server-2'): + config_data['dnsv6']['server2'] = c.return_value('dnsv6-server server-2') + if c.exists('dnsv6-server server-3'): + config_data['dnsv6']['server3'] = c.return_value('dnsv6-server server-3') if not c.exists('authentication mode noauth'): config_data['auth']['mech'] = c.return_value('authentication mode') if c.exists('authentication mode local'): @@ -274,6 +316,11 @@ def get_config(): config_data['auth']['radsettings']['dae-server']['port'] = c.return_value('authentication radius-settings dae-server port') if c.exists('authentication radius-settings dae-server secret'): config_data['auth']['radsettings']['dae-server']['secret'] = c.return_value('authentication radius-settings dae-server secret') + + if c.exists('client-ipv6-pool prefix'): + config_data['ipv6']['prfx'] = c.return_values('client-ipv6-pool prefix') + if c.exists('client-ipv6-pool delegate-prefix'): + config_data['ipv6']['pd'] = c.return_values('client-ipv6-pool delegate-prefix') return config_data @@ -288,7 +335,6 @@ def generate(c): tmpl = jinja2.Template(ipoe_config, trim_blocks=True) config_text = tmpl.render(c) - open(ipoe_cnf,'w').write(config_text) return c @@ -325,6 +371,8 @@ def verify(c): except: raise ConfigError("service ipoe-server authentication radius-settings dae-server port value required") + if len(c['ipv6']['pd']) != 0 and len(c['ipv6']['prfx']) == 0: + raise ConfigError("service ipoe-server client-ipv6-pool prefix needs a value") return c -- cgit v1.2.3 From 65f5e295c3dbe72ca3df831c552d7bc92389c958 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 3 Jul 2019 04:00:50 +0200 Subject: T1504: wait for commit lock before trying to update resolv.conf in the out of CLI mode. --- src/conf_mode/dns_forwarding.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 0ce2eee2c..b9a5b99e9 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -23,6 +23,8 @@ import argparse import jinja2 import netifaces +import vyos.util + from vyos.config import Config from vyos import ConfigError @@ -265,6 +267,12 @@ def apply(dns): if __name__ == '__main__': args = parser.parse_args() + + if args.dhclient: + # There's a big chance it was triggered by a commit still in progress + # so we need to wait until the new values are in the running config + vyos.util.wait_for_commit_lock() + try: c = get_config(args) verify(c) -- cgit v1.2.3 From acd5f855bcca98008719d0ef371be3076861b4f1 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 3 Jul 2019 12:41:33 +0200 Subject: T1497: remove the no longer necessary workaround for bad return_effective_values output. --- src/conf_mode/host_name.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 43f36dd35..b0a4648c7 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -124,10 +124,7 @@ def get_config(arguments): hosts['domain_search'].append(search) if conf.exists("system name-server"): - if not isinstance(conf.return_values("system name-server"), list): - hosts['nameserver'] = conf.return_values("system name-server").replace("'", "").split() - else: - hosts['nameserver'] = conf.return_values("system name-server") + hosts['nameserver'] = conf.return_values("system name-server") if conf.exists("system disable-dhcp-nameservers"): hosts['no_dhcp_ns'] = conf.exists('system disable-dhcp-nameservers') -- cgit v1.2.3 From 6b759f81fec573859798a6d3f971bfaa0b1960c6 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 3 Jul 2019 13:29:52 +0200 Subject: T1497: make host_name.py wait for commit lock too. --- src/conf_mode/host_name.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index b0a4648c7..6fb7031a8 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -29,6 +29,8 @@ import subprocess import argparse import jinja2 +import vyos.util + from vyos.config import Config from vyos import ConfigError @@ -273,6 +275,13 @@ def apply(config): if __name__ == '__main__': args = parser.parse_args() + + if args.dhclient: + # There's a big chance it was triggered by a commit still in progress + # so we need to wait until the new values are in the running config + vyos.util.wait_for_commit_lock() + + try: c = get_config(args) verify(c) -- cgit v1.2.3 From 377c04cbd7c11f3288664f9e64a95ee8fda23457 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Thu, 4 Jul 2019 12:15:52 +0200 Subject: T1435 plus other dhcp/dhcpv6-server enhancements - T1435: dhcp-server: make ip-address optional in static-mapping - remove [REQUIRED] from dhcpv6-server static-mapping identifier and ipv6-address - verify if static-mapping ipv6-address is in subnet - make help and error messages in conf-mode more descriptive - remove regex ^$ anchors (implied in re.fullmatch) --- interface-definitions/dhcp-server.xml | 41 ++++++++++++++++++--------------- interface-definitions/dhcpv6-server.xml | 29 ++++++++++++----------- src/conf_mode/dhcp_server.py | 21 ++++++++--------- src/conf_mode/dhcpv6_server.py | 16 +++++++++++++ 4 files changed, 64 insertions(+), 43 deletions(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/dhcp-server.xml b/interface-definitions/dhcp-server.xml index 87999f496..7d42294e8 100644 --- a/interface-definitions/dhcp-server.xml +++ b/interface-definitions/dhcp-server.xml @@ -46,9 +46,9 @@ DHCP shared network name [REQUIRED] - ^[-_a-zA-Z0-9.]+$ + [-_a-zA-Z0-9.]+ - Invalid DHCP pool name + Invalid shared network name. May only contain letters, numbers and .-_ @@ -151,7 +151,7 @@ - IP address that needs to be excluded from DHCP lease range + IP address to exclude from DHCP lease range ipv4 IPv4 address to exclude from lease range @@ -183,9 +183,9 @@ DHCP failover peer name [REQUIRED] - ^[-_a-zA-Z0-9.]+$ + [-_a-zA-Z0-9.]+ - Invalid failover peer name + Invalid failover peer name. May only contain letters, numbers and .-_ @@ -193,7 +193,7 @@ IP address of failover peer [REQUIRED] ipv4 - IPv4 address to exclude from lease range + IPv4 address of failover peer @@ -225,12 +225,12 @@ Lease timeout in seconds (default: 86400) 0-4294967295 - DHCP lease time in seconds must be between 0 and 4294967295 (49 days) + DHCP lease time in seconds - DHCP lease time must be 0 to 4294967295 + DHCP lease time must be between 0 and 4294967295 (49 days) @@ -288,9 +288,9 @@ DHCP lease range - ^[-_a-zA-Z0-9.]+$ + [-_a-zA-Z0-9.]+ - Invalid DHCP lease range name + Invalid DHCP lease range name. May only contain letters, numbers and .-_ @@ -321,22 +321,22 @@ - Static mapping for specified address type + Name of static mapping - ^[-_a-zA-Z0-9.]+$ + [-_a-zA-Z0-9.]+ - Invalid static-mapping name + Invalid static mapping name. May only contain letters, numbers and .-_ - Option to disable static-mapping + Option to disable static mapping - Static mapping for specified IP address [REQUIRED] + Fixed IP address of static mapping ipv4 IPv4 address used in static mapping @@ -348,7 +348,7 @@ - Static mapping for specified MAC address [REQUIRED] + MAC address of static mapping [REQUIRED] h:h:h:h:h:h MAC address used in static mapping [REQUIRED] @@ -358,6 +358,7 @@ Additional static-mapping parameters for DHCP server. + Will be placed inside the "host" block of the mapping. You must use the syntax of dhcpd.conf in this text-field. Using this without proper knowledge may result in a crashed DHCP server. Check system log to look for errors. @@ -414,10 +415,14 @@ Offset of the client's subnet in seconds from Coordinated Universal Time (UTC) + + [-]N + Time offset (number, may be negative) + - ^-?[0-9]+$ + -?[0-9]+ - Invalid time offset valuee + Invalid time offset value diff --git a/interface-definitions/dhcpv6-server.xml b/interface-definitions/dhcpv6-server.xml index e18a58608..28b56a64d 100644 --- a/interface-definitions/dhcpv6-server.xml +++ b/interface-definitions/dhcpv6-server.xml @@ -32,9 +32,9 @@ DHCPv6 shared network name [REQUIRED] - ^[-_a-zA-Z0-9.]+$ + [-_a-zA-Z0-9.]+ - Invalid DHCPv6 pool name + Invalid DHCPv6 shared network name. May only contain letters, numbers and .-_ @@ -112,9 +112,9 @@ Domain name for client to search - ^[-_a-zA-Z0-9.]+$ + [-_a-zA-Z0-9.]+ - Invalid domain name syntax + Invalid domain name. May only contain letters, numbers and .-_ @@ -157,9 +157,9 @@ NIS domain name for client to use - ^[-_a-zA-Z0-9.]+$ + [-_a-zA-Z0-9.]+ - Invalid NIS domain name syntax + Invalid NIS domain name @@ -179,9 +179,9 @@ NIS+ domain name for client to use - ^[-_a-zA-Z0-9.]+$ + [-_a-zA-Z0-9.]+ - Invalid NIS+ domain name syntax + Invalid NIS+ domain name. May only contain letters, numbers and .-_ @@ -260,9 +260,9 @@ SIP server name - ^[-_a-zA-Z0-9.]+$ + [-_a-zA-Z0-9.]+ - Invalid SIP server name syntax + Invalid SIP server name. May only contain letters, numbers and .-_ @@ -281,7 +281,7 @@ [-_a-zA-Z0-9.]+ - Invalid static-mapping name. May only contain letters, numbers and .-_ + Invalid static mapping name. May only contain letters, numbers and .-_ @@ -292,7 +292,7 @@ - Client identifier (DUID) for this static mapping [REQUIRED] + Client identifier (DUID) for this static mapping h[[:h]...] DUID: colon-separated hex list (as used by isc-dhcp option dhcpv6.client-id) @@ -300,14 +300,15 @@ ([0-9A-Fa-f]{1,2}[:])*([0-9A-Fa-f]{1,2}) + Invalid DUID. Must be in the format h[[:h]...] where each \"h\" is 1 to 2 hex characters. - Client IPv6 address for this static mapping [REQUIRED] + Client IPv6 address for this static mapping ipv6 - IPv6 address for this static mapping [REQUIRED] + IPv6 address for this static mapping diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index 78927a847..6d88ea95a 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -187,7 +187,9 @@ shared-network {{ network.name }} { {%- for host in subnet.static_mapping %} {% if not host.disabled -%} host {% if host_decl_name -%} {{ host.name }} {%- else -%} {{ network.name }}_{{ host.name }} {%- endif %} { + {%- if host.ip_address %} fixed-address {{ host.ip_address }}; + {%- endif %} hardware ethernet {{ host.mac_address }}; {%- if host.static_parameters %} # The following {{ host.static_parameters | length }} line(s) were added as static-mapping-parameters in the CLI and have not been validated @@ -728,22 +730,19 @@ def verify(dhcp): raise ConfigError('No DHCP address range or active static-mapping set\n' \ 'for subnet {0}!'.format(subnet['network'])) - # Static IP address mappings require both an IP address and MAC address + # Static mappings require just a MAC address (will use an IP from the dynamic pool if IP is not set) for mapping in subnet['static_mapping']: - # Static IP address must be configured - if not mapping['ip_address']: - raise ConfigError('DHCP static lease IP address not specified for static mapping\n' \ - '{0} under shared network name {1}!'.format(mapping['name'], network['name'])) - # Static IP address must be in bound - if not ipaddress.ip_address(mapping['ip_address']) in ipaddress.ip_network(subnet['network']): - raise ConfigError('DHCP static lease IP address {0} for static mapping {1}\n' \ - 'in shared network {2} is outside DHCP lease subnet {3}!' \ - .format(mapping['ip_address'], mapping['name'], network['name'], subnet['network'])) + if mapping['ip_address']: + # Static IP address must be in bound + if not ipaddress.ip_address(mapping['ip_address']) in ipaddress.ip_network(subnet['network']): + raise ConfigError('DHCP static lease IP address {0} for static mapping {1}\n' \ + 'in shared network {2} is outside DHCP lease subnet {3}!' \ + .format(mapping['ip_address'], mapping['name'], network['name'], subnet['network'])) # Static mapping requires MAC address if not mapping['mac_address']: - raise ConfigError('DHCP static lease MAC address not specified for static mapping\n' \ + raise ConfigError('DHCP static lease MAC address not specified for static mapping\n' \ '{0} under shared network name {1}!'.format(mapping['name'], network['name'])) # There must be one subnet connected to a listen interface. diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index f5117de53..d2769466e 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -94,8 +94,12 @@ shared-network {{ network.name }} { {%- for host in subnet.static_mapping %} {% if not host.disabled -%} host {{ network.name }}_{{ host.name }} { + {%- if host.client_identifier %} host-identifier option dhcp6.client-id {{ host.client_identifier }}; + {%- endif %} + {%- if host.ipv6_address %} fixed-address6 {{ host.ipv6_address }}; + {%- endif %} } {%- endif %} {%- endfor %} @@ -384,7 +388,19 @@ def verify(dhcpv6): raise ConfigError('DHCPv6 prefix {0} is not in subnet {1}\n' \ 'specified for shared network {2}!'.format(prefix['prefix'], subnet['network'], network['name'])) + # Static mappings don't require anything (but check if IP is in subnet if it's set) + for mapping in subnet['static_mapping']: + if mapping['ipv6_address']: + # Static address must be in subnet + if not ipaddress.ip_address(mapping['ipv6_address']) in ipaddress.ip_network(subnet['network']): + raise ConfigError('DHCPv6 static mapping IPv6 address {0} for static mapping {1}\n' \ + 'in shared network {2} is outside subnet {3}!' \ + .format(mapping['ipv6_address'], mapping['name'], network['name'], subnet['network'])) + # DHCPv6 requires at least one configured address range or one static mapping + # (FIXME: is not actually checked right now?) + + # There must be one subnet connected to a listen interface if network is not disabled. if not network['disabled']: if vyos.validate.is_subnet_connected(subnet['network']): listen_ok = True -- cgit v1.2.3 From f17f897a315d3177c0c42dd3d3b7dadf307a33c8 Mon Sep 17 00:00:00 2001 From: hagbard Date: Fri, 5 Jul 2019 10:21:14 -0700 Subject: [PPPoE] - T1489: vlan_mon config options --- interface-definitions/pppoe-server.xml | 27 +++++++++++++++++++--- src/conf_mode/accel_pppoe.py | 19 +++++++++++----- src/migration-scripts/pppoe-server/1-to-2 | 38 +++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 9 deletions(-) create mode 100755 src/migration-scripts/pppoe-server/1-to-2 (limited to 'src/conf_mode') diff --git a/interface-definitions/pppoe-server.xml b/interface-definitions/pppoe-server.xml index 18b0e649c..aab830f1e 100644 --- a/interface-definitions/pppoe-server.xml +++ b/interface-definitions/pppoe-server.xml @@ -337,15 +337,36 @@ - + + interface(s) to listen on - - + + + + VLAN monitor for the automatic creation of vlans (user per vlan) + + + + VLAN ID needs to be between 1 and 4096 + + + + + + VLAN monitor for the automatic creation of vlans (user per vlan) + + (409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2})-(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2}) + + + + + + local gateway address diff --git a/src/conf_mode/accel_pppoe.py b/src/conf_mode/accel_pppoe.py index 3c7759b17..9c879502a 100755 --- a/src/conf_mode/accel_pppoe.py +++ b/src/conf_mode/accel_pppoe.py @@ -242,13 +242,16 @@ ac-name={{concentrator}} {% if interface %} {% for int in interface %} interface={{int}} -{% endfor %} +{% if interface[int]['vlans'] %} +vlan_mon={{interface[int]['vlans']|join(',')}} +interface=re:{{int}}\.(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2}) {% endif %} +{% endfor -%} +{% endif -%} {% if svc_name %} service-name={{svc_name}} -{% endif %} +{% endif -%} pado-delay=0 -# maybe: called-sid, tr101, padi-limit etc. {% if limits %} [connlimit] @@ -338,7 +341,7 @@ def get_config(): 'client_ip_pool' : '', 'client_ip_subnets' : [], 'client_ipv6_pool' : {}, - 'interface' : [], + 'interface' : {}, 'ppp_gw' : '', 'svc_name' : '', 'dns' : [], @@ -357,7 +360,12 @@ def get_config(): if c.exists('service-name'): config_data['svc_name'] = c.return_value('service-name') if c.exists('interface'): - config_data['interface'] = c.return_values('interface') + for intfc in c.list_nodes('interface'): + config_data['interface'][intfc] = {'vlans' : []} + if c.exists('interface ' + intfc + ' vlan-id'): + config_data['interface'][intfc]['vlans'] += c.return_values('interface ' + intfc + ' vlan-id') + if c.exists('interface ' + intfc + ' vlan-range'): + config_data['interface'][intfc]['vlans'] +=c.return_values('interface ' + intfc + ' vlan-range') if c.exists('local-ip'): config_data['ppp_gw'] = c.return_value('local-ip') if c.exists('dns-servers'): @@ -491,7 +499,6 @@ def get_config(): if c.exists('authentication radius-settings rate-limit vendor'): config_data['authentication']['radiusopt']['shaper']['vendor'] = c.return_value('authentication radius-settings rate-limit vendor') - if c.exists('mtu'): config_data['mtu'] = c.return_value('mtu') diff --git a/src/migration-scripts/pppoe-server/1-to-2 b/src/migration-scripts/pppoe-server/1-to-2 new file mode 100755 index 000000000..fa83896d3 --- /dev/null +++ b/src/migration-scripts/pppoe-server/1-to-2 @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +# Convert "service pppoe-server interface ethX" +# to: +# "service pppoe-server interface ethX {}" + +import sys + +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +ctree = ConfigTree(config_file) +cbase = ['service', 'pppoe-server','interface'] + +if not ctree.exists(cbase): + sys.exit(0) +else: + nics = ctree.return_values(cbase) + # convert leafNode to a tagNode + ctree.set(cbase) + ctree.set_tag(cbase) + for nic in nics: + ctree.set(cbase + [nic]) + + try: + open(file_name,'w').write(ctree.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) + -- cgit v1.2.3 From 14b4fa607f37b051957abc9cbe01014a610adc81 Mon Sep 17 00:00:00 2001 From: hagbard Date: Mon, 8 Jul 2019 12:15:09 -0700 Subject: [IPoE] - T1510: vlan-mon option implementation --- interface-definitions/ipoe-server.xml | 19 +++++++++++++++++++ src/conf_mode/ipoe_server.py | 16 ++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/ipoe-server.xml b/interface-definitions/ipoe-server.xml index 46ac2357a..6c93d3699 100644 --- a/interface-definitions/ipoe-server.xml +++ b/interface-definitions/ipoe-server.xml @@ -90,6 +90,25 @@ + + + VLAN monitor for the automatic creation of vlans (user per vlan) + + + + VLAN ID needs to be between 1 and 4096 + + + + + + VLAN monitor for the automatic creation of vlans (user per vlan) + + (409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2})-(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2}) + + + + diff --git a/src/conf_mode/ipoe_server.py b/src/conf_mode/ipoe_server.py index 45c64c617..ca6b423e5 100755 --- a/src/conf_mode/ipoe_server.py +++ b/src/conf_mode/ipoe_server.py @@ -81,6 +81,13 @@ username=ifname password=csid {% endif %} +{%- for intfc in interfaces %} +{% if (interfaces[intfc]['shared'] == '0') and (interfaces[intfc]['vlan_mon']) %} +vlan_mon={{interfaces[intfc]['vlan_mon']|join(',')}} +interface=re:{{intfc}}\.(409[0-6]|40[0-8][0-9]|[1-3][0-9]{3}|[1-9][0-9]{0,2}) +{% endif %} +{% endfor %} + {% if (dns['server1']) or (dns['server2']) %} [dns] {% if dns['server1'] %} @@ -230,7 +237,8 @@ def get_config(): 'shared' : '1', 'sess_start' : 'dhcpv4', ### may need a conifg option, can be dhcpv4 or up for unclassified pkts 'range' : None, - 'ifcfg' : '1' + 'ifcfg' : '1', + 'vlan_mon' : [] } config_data['dns'] = { 'server1' : None, @@ -258,6 +266,10 @@ def get_config(): config_data['interfaces'][intfc]['mode'] = c.return_value('interface ' + intfc + ' network-mode') if c.return_value('interface ' + intfc + ' network') == 'vlan': config_data['interfaces'][intfc]['shared'] = '0' + if c.exists('interface ' + intfc + ' vlan-id'): + config_data['interfaces'][intfc]['vlan_mon'] += c.return_values('interface ' + intfc + ' vlan-id') + if c.exists('interface ' + intfc + ' vlan-range'): + config_data['interfaces'][intfc]['vlan_mon'] += c.return_values('interface ' + intfc + ' vlan-range') if c.exists('interface ' + intfc + ' client-subnet'): config_data['interfaces'][intfc]['range'] = c.return_value('interface ' + intfc + ' client-subnet') if c.exists('dns-server server-1'): @@ -321,7 +333,7 @@ def get_config(): config_data['ipv6']['prfx'] = c.return_values('client-ipv6-pool prefix') if c.exists('client-ipv6-pool delegate-prefix'): config_data['ipv6']['pd'] = c.return_values('client-ipv6-pool delegate-prefix') - + return config_data def generate(c): -- cgit v1.2.3 From 02e5bb55d3922516cfe53016202d58924b72950a Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 9 Jul 2019 18:07:50 +0200 Subject: T1497: remove duplicate name servers and search domains obtained from DHCP. --- src/conf_mode/host_name.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 6fb7031a8..eb4b339e9 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -209,6 +209,12 @@ def generate(config): config['nameserver'] += dhcp_ns config['domain_search'] += dhcp_sd + # Prune duplicate values + # Not order preserving, but then when multiple DHCP clients are used, + # there can't be guarantees about the order anyway + dhcp_ns = list(set(dhcp_ns)) + dhcp_sd = list(set(dhcp_sd)) + # We have third party scripts altering /etc/hosts, too. # One example are the DHCP hostname update scripts thus we need to cache in # every modification first - so changing domain-name, domain-search or hostname -- cgit v1.2.3 From 65b2f36a77f311a207b8e5406d222f4dbef177cf Mon Sep 17 00:00:00 2001 From: hagbard Date: Tue, 9 Jul 2019 09:58:16 -0700 Subject: [wireguard] - T1516: changing committed config causes error --- src/conf_mode/wireguard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/wireguard.py b/src/conf_mode/wireguard.py index b6c1e189b..8234fad0b 100755 --- a/src/conf_mode/wireguard.py +++ b/src/conf_mode/wireguard.py @@ -225,7 +225,7 @@ def apply(c): ### config updates if c['interfaces'][intf]['status'] == 'exists': ### IP address change - addr_eff = re.sub("\'", "", c_eff.return_effective_values(intf + ' address')).split() + addr_eff = c_eff.return_effective_values(intf + ' address') addr_rem = list(set(addr_eff) - set(c['interfaces'][intf]['addr'])) addr_add = list(set(c['interfaces'][intf]['addr']) - set(addr_eff)) -- cgit v1.2.3 From 5c4e5e9a6a893aa2fb0df50cf327e942b52995b9 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Fri, 12 Jul 2019 15:31:10 +0200 Subject: Do not try to verify the hostname config if the script is run by cloud-init. --- src/conf_mode/host_name.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index eb4b339e9..1988f7b4f 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -117,6 +117,10 @@ def get_config(arguments): if conf.exists("system host-name"): hosts['hostname'] = conf.return_value("system host-name") + # This may happen if the config is not loaded yet, + # e.g. if run by cloud-init + if not hosts['hostname']: + hosts['hostname'] = default_config_data['hostname'] if conf.exists("system domain-name"): hosts['domain_name'] = conf.return_value("system domain-name") @@ -290,7 +294,12 @@ if __name__ == '__main__': try: c = get_config(args) - verify(c) + # If it's called from dhclient, then either: + # a) verification was already done at commit time + # b) it's run on an unconfigured system, e.g. by cloud-init + # Therefore, verification is either redundant or useless + if not args.dhclient: + verify(c) generate(c) apply(c) except ConfigError as e: -- cgit v1.2.3 From a8a92eb3fcef8ed911ebcaedd32dc5b948c6c79a Mon Sep 17 00:00:00 2001 From: Eshenko Dmitriy Date: Sun, 14 Jul 2019 00:06:00 +0300 Subject: Fix bind param if outside-address not present If in config exist `bind=` without value, accel-ppp listen wrong ip address 255.255.255.255:1723. If need default behavior with listening on 0.0.0.0:1723 we don't set empty bind option. --- src/conf_mode/accel_pptp.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/accel_pptp.py b/src/conf_mode/accel_pptp.py index 6c53e8dd4..5f8ccfd84 100755 --- a/src/conf_mode/accel_pptp.py +++ b/src/conf_mode/accel_pptp.py @@ -84,7 +84,9 @@ wins2={{wins[1]}} {% endif %} [pptp] +{% if outside_addr %} bind={{outside_addr}} +{% endif %} verbose=5 ppp-max-mtu={{mtu}} mppe={{authentication['mppe']}} -- cgit v1.2.3 From 5886dd27cbc65f8cda04752bbd39a960b0887523 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 14 Jul 2019 10:59:07 +0200 Subject: [dns-forwarding] T1333: handle domain forward and general recursion in one configuration line In the past we used the PowerDNS cofniguration option forward-zones and forward-zones-recurse, but only the latter one sets the recursion bit in the DNS query. Thus all recursions have been moved to this config statement. --- src/conf_mode/dns_forwarding.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index aab389074..3ca77adee 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -65,21 +65,19 @@ local-address={{ listen_on | join(',') }} # dnssec dnssec={{ dnssec }} -{% if name_servers -%} -# name-server -forward-zones-recurse=.={{ name_servers | join(';') }} -{% else %} -# no name-servers specified - start full recursor -{% endif %} - -# domain ... server ... -{% if domains -%} - -forward-zones-recurse={% for d in domains %} +# forward-zones / recursion +# +# statement is only inserted if either one forwarding domain or nameserver is configured +# if nothing is given at all, powerdns will act as a real recursor and resolve all requests by its own +# +{% if name_servers or domains %}forward-zones-recurse= +{%- for d in domains %} {{ d.name }}={{ d.servers | join(";") }} -{{- "," if not loop.last -}} -{% endfor %} - +{{- ", " if not loop.last -}} +{%- endfor -%} +{%- if name_servers -%} +{%- if domains -%}, {% endif -%}.={{ name_servers | join(';') }} +{% endif %} {% endif %} """ @@ -248,7 +246,6 @@ def generate(dns): return None tmpl = jinja2.Template(config_tmpl, trim_blocks=True) - config_text = tmpl.render(dns) with open(config_file, 'w') as f: f.write(config_text) -- cgit v1.2.3 From 5159252e53b620a1a6345f7651f122ef6c82bb07 Mon Sep 17 00:00:00 2001 From: Eshenko Dmitriy Date: Mon, 15 Jul 2019 20:10:49 +0300 Subject: Fix typo pppoe-server to pptp-server --- src/conf_mode/accel_pptp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/accel_pptp.py b/src/conf_mode/accel_pptp.py index 5f8ccfd84..1dd7efb3e 100755 --- a/src/conf_mode/accel_pptp.py +++ b/src/conf_mode/accel_pptp.py @@ -296,7 +296,7 @@ def verify(c): if c['authentication']['mode'] == 'local': if not c['authentication']['local-users']: - raise ConfigError('pppoe-server authentication local-users required') + raise ConfigError('pptp-server authentication local-users required') for usr in c['authentication']['local-users']: if not c['authentication']['local-users'][usr]['passwd']: raise ConfigError('user ' + usr + ' requires a password') -- cgit v1.2.3 From 334677572aef752b0bf2c893bd14bdf6f801bb4b Mon Sep 17 00:00:00 2001 From: hagbard Date: Mon, 15 Jul 2019 11:25:12 -0700 Subject: [T1299] - SNMP extension with custom scripts --- interface-definitions/snmp.xml | 22 ++++++++++++++++++++++ src/conf_mode/snmp.py | 27 +++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) (limited to 'src/conf_mode') diff --git a/interface-definitions/snmp.xml b/interface-definitions/snmp.xml index 103aa39d7..8b2fa8a93 100644 --- a/interface-definitions/snmp.xml +++ b/interface-definitions/snmp.xml @@ -599,6 +599,28 @@ + + + SNMP script extensions + + + + + Extension name + + + + + Script location and name + + + + + + + + + diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index 06d2e253a..0ddab2129 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -206,6 +206,13 @@ group {{ u.group }} usm {{ u.name }} group {{ u.group }} tsm {{ u.name }} {% endfor %} {%- endif %} + +{% if script_ext %} +# extension scripts +{%- for ext in script_ext|sort %} +extend\t{{ext}}\t{{script_ext[ext]}} +{%- endfor %} +{% endif %} """ # SNMP template (/etc/default/snmpd) - be careful if you edit the template. @@ -240,7 +247,8 @@ default_config_data = { 'v3_tsm_key': '', 'v3_tsm_port': '10161', 'v3_users': [], - 'v3_views': [] + 'v3_views': [], + 'script_ext': {} } def rmfile(file): @@ -345,6 +353,14 @@ def get_config(): snmp['trap_targets'].append(trap_tgt) + # + # 'set service snmp script-extensions' + # + if conf.exists('script-extensions'): + for extname in conf.list_nodes('script-extensions extension-name'): + snmp['script_ext'][extname] = '/config/user-data/' + conf.return_value('script-extensions extension-name ' + extname + ' script') + + ######################################################################### # ____ _ _ __ __ ____ _____ # # / ___|| \ | | \/ | _ \ __ _|___ / # @@ -581,6 +597,14 @@ def verify(snmp): if snmp is None: return None + ### check if the configured script actually exist under /config/user-data + if snmp['script_ext']: + for ext in snmp['script_ext']: + if not os.path.isfile(snmp['script_ext'][ext]): + print ("WARNING: script: " + snmp['script_ext'][ext] + " doesn\'t exist") + else: + os.chmod(snmp['script_ext'][ext], 0o555) + # bail out early if SNMP v3 is not configured if not snmp['v3_enabled']: return None @@ -633,7 +657,6 @@ def verify(snmp): if not 'seclevel' in group.keys(): raise ConfigError('"seclevel" must be specified') - if 'v3_traps' in snmp.keys(): for trap in snmp['v3_traps']: if trap['authPassword'] and trap['authMasterKey']: -- cgit v1.2.3 From 016524841da12b68468468cf8f4947b82213ffcd Mon Sep 17 00:00:00 2001 From: hagbard Date: Tue, 16 Jul 2019 12:04:47 -0700 Subject: [syslog] T1530 - "set system syslog global archive file" doesn't work --- src/conf_mode/syslog.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/syslog.py b/src/conf_mode/syslog.py index 105f35657..7b79c701b 100755 --- a/src/conf_mode/syslog.py +++ b/src/conf_mode/syslog.py @@ -72,8 +72,8 @@ logrotate_configs = ''' missingok notifempty create - rotate {{files[file]['max-files']}} - size={{ files[file]['max-size']//1024}}k + rotate {{files[file]['max-files']}} + size={{files[file]['max-size']//1024}}k postrotate invoke-rc.d rsyslog rotate > /dev/null endscript @@ -120,8 +120,8 @@ def get_config(): config_data['files']['global']['selectors'] = generate_selectors(c, 'global facility') if c.exists('global archive size'): config_data['files']['global']['max-size'] = int(c.return_value('global archive size'))* 1024 - if c.exists('global archive files'): - config_data['files']['global']['max-files'] = c.return_value('global archive files') + if c.exists('global archive file'): + config_data['files']['global']['max-files'] = c.return_value('global archive file') if c.exists('global preserve-fqdn'): config_data['files']['global']['preserver_fqdn'] = True @@ -196,7 +196,7 @@ def get_config(): } } ) - + return config_data def generate_selectors(c, config_node): -- cgit v1.2.3 From f62f31fb14aeaff70edb53d0be2d501916e8e39c Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 16 Jul 2019 23:02:32 +0200 Subject: T1531: do not include the domain name in system hostname. --- src/conf_mode/host_name.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 1988f7b4f..16467c8df 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -258,18 +258,17 @@ def apply(config): if config is None: return None - fqdn = config['hostname'] - if config['domain_name']: - fqdn += '.' + config['domain_name'] + # No domain name -- the Debian way. + hostname_new = config['hostname'] # rsyslog runs into a race condition at boot time with systemd # restart rsyslog only if the hostname changed. - hn = subprocess.check_output(['hostnamectl', '--static']).decode().strip() + hostname_old = subprocess.check_output(['hostnamectl', '--static']).decode().strip() - os.system("hostnamectl set-hostname --static {0}".format(fqdn.rstrip('.'))) + os.system("hostnamectl set-hostname --static {0}".format(hostname_new)) # Restart services that use the hostname - if hn != fqdn: + if hostname_new != hostname_old: os.system("systemctl restart rsyslog.service") # If SNMP is running, restart it too -- cgit v1.2.3 From fdcce3a65e556eed1ced4156d71bbc32b3c87d5a Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 18 Jul 2019 23:55:37 +0200 Subject: T1440: in IPv4 DHCP, print the subnet rather than a dict dump when a non-unique subnet is found. --- src/conf_mode/dhcp_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index 6d88ea95a..3e1381cd0 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -753,7 +753,7 @@ def verify(dhcp): # Subnets must be non overlapping if subnet['network'] in subnets: - raise ConfigError('DHCP subnets must be unique! Subnet {0} defined multiple times!'.format(subnet)) + raise ConfigError('DHCP subnets must be unique! Subnet {0} defined multiple times!'.format(subnet['network'])) else: subnets.append(subnet['network']) -- cgit v1.2.3 From 4236848372a4565141167877f2eb32b0eedae577 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Fri, 19 Jul 2019 00:03:07 +0200 Subject: [DHCPv6 server] T1440: add subnet uniqueness check to DHCPv6. --- src/conf_mode/dhcpv6_server.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/conf_mode') diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py index d2769466e..039321430 100755 --- a/src/conf_mode/dhcpv6_server.py +++ b/src/conf_mode/dhcpv6_server.py @@ -397,6 +397,12 @@ def verify(dhcpv6): 'in shared network {2} is outside subnet {3}!' \ .format(mapping['ipv6_address'], mapping['name'], network['name'], subnet['network'])) + # Subnets must be unique + if subnet['network'] in subnets: + raise ConfigError('DHCPv6 subnets must be unique! Subnet {0} defined multiple times!'.format(subnet['network'])) + else: + subnets.append(subnet['network']) + # DHCPv6 requires at least one configured address range or one static mapping # (FIXME: is not actually checked right now?) -- cgit v1.2.3 From c0b7e14cb2adf5e686a46d5d25c4aed63bac9f53 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Fri, 19 Jul 2019 00:41:54 +0200 Subject: [VRRP] T1362: quote VRRP password strings to avoid config parse errors. --- src/conf_mode/vrrp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode') diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py index 85c6ad580..a08493309 100755 --- a/src/conf_mode/vrrp.py +++ b/src/conf_mode/vrrp.py @@ -85,7 +85,7 @@ vrrp_instance {{ group.name }} { {% if group.auth_password -%} authentication { - auth_pass {{ group.auth_password }} + auth_pass "{{ group.auth_password }}" auth_type {{ group.auth_type }} } {% endif -%} -- cgit v1.2.3