From 36e5f07f8dda51cc5bb0a105077e751f1c851435 Mon Sep 17 00:00:00 2001 From: Lucas Christian Date: Thu, 7 Oct 2021 20:41:02 -0700 Subject: T562: Config syntax for defining DNS forward authoritative zones --- data/templates/dns-forwarding/recursor.conf.tmpl | 3 + .../recursor.vyos-hostsd.conf.lua.tmpl | 6 + .../dns-forwarding/recursor.zone.conf.tmpl | 7 + interface-definitions/dns-forwarding.xml.in | 452 ++++++++++++++++++++- .../include/dns/time-to-live.xml.i | 15 + python/vyos/hostsd_client.py | 12 + src/conf_mode/dns_forwarding.py | 206 +++++++++- src/services/vyos-hostsd | 36 +- 8 files changed, 730 insertions(+), 7 deletions(-) create mode 100644 data/templates/dns-forwarding/recursor.zone.conf.tmpl create mode 100644 interface-definitions/include/dns/time-to-live.xml.i diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl index d44f756e8..02efe903b 100644 --- a/data/templates/dns-forwarding/recursor.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.tmpl @@ -31,5 +31,8 @@ dnssec={{ dnssec }} # serve rfc1918 records serve-rfc1918={{ 'no' if no_serve_rfc1918 is defined else 'yes' }} +# zones +auth-zones={% for z in authoritative_zones %}{{ z.name }}={{ z.file }}{{- "," if not loop.last -}}{% endfor %} + forward-zones-file=recursor.forward-zones.conf diff --git a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl index 784d5c360..7f29c387e 100644 --- a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl +++ b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl @@ -22,3 +22,9 @@ addNTA("{{ zone }}", "static") {% endfor %} {% endif %} +{% if authoritative_zones is defined %} +-- from 'service dns forwarding authoritative-domain' +{% for zone in authoritative_zones %} +addNTA("{{ zone }}", "static") +{% endfor %} +{% endif %} diff --git a/data/templates/dns-forwarding/recursor.zone.conf.tmpl b/data/templates/dns-forwarding/recursor.zone.conf.tmpl new file mode 100644 index 000000000..758871bef --- /dev/null +++ b/data/templates/dns-forwarding/recursor.zone.conf.tmpl @@ -0,0 +1,7 @@ +; +; Autogenerated by dns_forwarding.py +; +; +{% for r in records %} +{{ r.name }} {{ r.ttl }} {{ r.type }} {{ r.value }} +{% endfor %} diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index 5b0c87597..ced138bff 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -105,6 +105,456 @@ + + + Domain to host authoritative records for + + text + An absolute DNS name + + + ^[-_a-zA-Z0-9.]{1,63}$ + + + + + + DNS zone records + + + + + "A" record + + text + A DNS name relative to the root record + + + @ + Root record + + + ^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$ + + + + + + IPv4 address [REQUIRED] + + ipv4 + IPv4 address + + + + + + + + #include + #include + + + + + "AAAA" record + + text + A DNS name relative to the root record + + + @ + Root record + + + ^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$ + + + + + + IPv6 address [REQUIRED] + + ipv6 + IPv6 address + + + + + + + + #include + #include + + + + + "CNAME" record + + text + A DNS name relative to the root record + + + @ + Root record + + + ^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$ + + + + + + Target DNS name [REQUIRED] + + name.example.com + An absolute DNS name + + + ^[-_a-zA-Z0-9.]{1,63}(?<!\.)$ + + + + #include + #include + + + + + "MX" record + + text + A DNS name relative to the root record + + + @ + Root record + + + ^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$ + + + + + + Mail server [REQUIRED] + + name.example.com + An absolute DNS name + + + ^[-_a-zA-Z0-9.]{1,63}(?<!\.)$ + + + + + + Server priority + + u32:1-999 + Server priority (lower numbers are higher priority) + + + + + + 10 + + + + #include + #include + + + + + "PTR" record + + text + A DNS name relative to the root record + + + @ + Root record + + + ^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$ + + + + + + Target DNS name [REQUIRED] + + name.example.com + An absolute DNS name + + + ^[-_a-zA-Z0-9.]{1,63}(?<!\.)$ + + + + #include + #include + + + + + "TXT" record + + text + A DNS name relative to the root record + + + @ + Root record + + + ^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$ + + + + + + Record contents [REQUIRED] + + text + Record contents + + + + + #include + #include + + + + + "SPF" record (type=SPF) + + text + A DNS name relative to the root record + + + @ + Root record + + + ^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$ + + + + + + Record contents [REQUIRED] + + text + Record contents + + + + #include + #include + + + + + Host an DNS "SRV" record + + text + A DNS name relative to the root record + + + @ + Root record + + + ^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$ + + + + + + Service entry [REQUIRED] + + u32:0-65535 + Entry number + + + + + + + + + Server hostname [REQUIRED] + + name.example.com + An absolute DNS name + + + ^[-_a-zA-Z0-9.]{1,63}(?<!\.)$ + + + + + + Port number [REQUIRED] + + u32:0-65535 + TCP/UDP port number + + + + + + + + + Entry priority + + u32:0-65535 + Entry priority (lower numbers are higher priority) + + + + + + 10 + + + + Entry weight + + u32:0-65535 + Entry weight + + + + + + 0 + + + + #include + #include + + + + + "NAPTR" record + + text + A DNS name relative to the root record + + + @ + Root record + + + ^([-_a-zA-Z0-9.]{1,63}|@)(?<!\.)$ + + + + + + NAPTR rule [REQUIRED] + + u32:0-65535 + Rule number + + + + + + + + + Rule order + + u32:0-65535 + Rule order (lower order is evaluated first) + + + + + + + + + Rule preference + + u32:0-65535 + Rule preference + + + + + + 0 + + + + "S" flag + + + + + + "A" flag + + + + + + "U" flag + + + + + + "P" flag + + + + + + Service type + + ^[a-zA-Z][a-zA-Z0-9]{0,31}(\+[a-zA-Z][a-zA-Z0-9]{0,31})?$ + + + + + + Regular expression + + + + + Replacement DNS name + + name.example.com + An absolute DNS name + + + ^[-_a-zA-Z0-9.]{1,63}(?<!\.)$ + + + + + + #include + #include + + + + + #include + + Do not use local /etc/hosts file in name resolution @@ -114,7 +564,7 @@ Makes the server authoritatively not aware of RFC1918 addresses - + diff --git a/interface-definitions/include/dns/time-to-live.xml.i b/interface-definitions/include/dns/time-to-live.xml.i new file mode 100644 index 000000000..5c1a1472d --- /dev/null +++ b/interface-definitions/include/dns/time-to-live.xml.i @@ -0,0 +1,15 @@ + + + + Time-to-live (TTL) + + u32:0-2147483647 + TTL in seconds + + + + + + 300 + + diff --git a/python/vyos/hostsd_client.py b/python/vyos/hostsd_client.py index 303b6ea47..f31ef51cf 100644 --- a/python/vyos/hostsd_client.py +++ b/python/vyos/hostsd_client.py @@ -79,6 +79,18 @@ class Client(object): msg = {'type': 'forward_zones', 'op': 'get'} return self._communicate(msg) + def add_authoritative_zones(self, data): + msg = {'type': 'authoritative_zones', 'op': 'add', 'data': data} + self._communicate(msg) + + def delete_authoritative_zones(self, data): + msg = {'type': 'authoritative_zones', 'op': 'delete', 'data': data} + self._communicate(msg) + + def get_authoritative_zones(self): + msg = {'type': 'authoritative_zones', 'op': 'get'} + return self._communicate(msg) + def add_search_domains(self, data): msg = {'type': 'search_domains', 'op': 'add', 'data': data} self._communicate(msg) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 06366362a..c2816589c 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -17,6 +17,7 @@ import os from sys import exit +from glob import glob from vyos.config import Config from vyos.configdict import dict_merge @@ -50,10 +51,12 @@ def get_config(config=None): if not conf.exists(base): return None - dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. + # options which we need to update into the dictionary retrieved. default_values = defaults(base) + # T2665 due to how defaults under tag nodes work, we must clear these out before we merge + del default_values['authoritative_domain'] dns = dict_merge(default_values, dns) # some additions to the default dictionary @@ -66,6 +69,183 @@ def get_config(config=None): if conf.exists(base_nameservers_dhcp): dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)}) + if 'authoritative_domain' in dns: + dns['authoritative_zones'] = [] + dns['authoritative_zone_errors'] = [] + for node in dns['authoritative_domain']: + zonedata = dns['authoritative_domain'][node] + if ('disable' in zonedata) || (not 'records' in zonedata): + continue + zone = { + 'name': node, + 'file': "{}/zone.{}.conf".format(pdns_rec_run_dir, node), + 'records': [], + } + + recorddata = zonedata['records'] + + for rtype in [ 'a', 'aaaa', 'cname', 'mx', 'ptr', 'txt', 'spf', 'srv', 'naptr' ]: + if rtype not in recorddata: + continue + for subnode in recorddata[rtype]: + if 'disable' in recorddata[rtype][subnode]: + continue + + rdata = recorddata[rtype][subnode] + + if rtype in [ 'a', 'aaaa' ]: + rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + rdata = dict_merge(rdefaults, rdata) + + if not 'address' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: at least one address is required'.format(subnode, node)) + continue + + for address in rdata['address']: + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': address + }) + elif rtype in ['cname', 'ptr']: + rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + rdata = dict_merge(rdefaults, rdata) + + if not 'target' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: target is required'.format(subnode, node)) + continue + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{}.'.format(rdata['target']) + }) + elif rtype == 'mx': + rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + del rdefaults['server'] + rdata = dict_merge(rdefaults, rdata) + + if not 'server' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: at least one server is required'.format(subnode, node)) + continue + + for servername in rdata['server']: + serverdata = rdata['server'][servername] + serverdefaults = defaults(base + ['authoritative-domain', rtype, 'server']) # T2665 + serverdata = dict_merge(serverdefaults, serverdata) + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{} {}.'.format(serverdata['priority'], servername) + }) + elif rtype == 'txt': + rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + rdata = dict_merge(rdefaults, rdata) + + if not 'value' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: at least one value is required'.format(subnode, node)) + continue + + for value in rdata['value']: + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': "\"{}\"".format(value.replace("\"", "\\\"")) + }) + elif rtype == 'spf': + rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + rdata = dict_merge(rdefaults, rdata) + + if not 'value' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: value is required'.format(subnode, node)) + continue + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '"{}"'.format(rdata['value'].replace("\"", "\\\"")) + }) + elif rtype == 'srv': + rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + del rdefaults['entry'] + rdata = dict_merge(rdefaults, rdata) + + if not 'entry' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: at least one entry is required'.format(subnode, node)) + continue + + for entryno in rdata['entry']: + entrydata = rdata['entry'][entryno] + entrydefaults = defaults(base + ['authoritative-domain', rtype, 'entry']) # T2665 + entrydata = dict_merge(entrydefaults, entrydata) + + if not 'hostname' in entrydata: + dns['authoritative_zone_errors'].append('{}.{}: hostname is required for entry {}'.format(subnode, node, entryno)) + continue + + if not 'port' in entrydata: + dns['authoritative_zone_errors'].append('{}.{}: port is required for entry {}'.format(subnode, node, entryno)) + continue + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{} {} {} {}.'.format(entrydata['priority'], entrydata['weight'], entrydata['port'], entrydata['hostname']) + }) + elif rtype == 'naptr': + rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + del rdefaults['rule'] + rdata = dict_merge(rdefaults, rdata) + + + if not 'rule' in rdata: + dns['authoritative_zone_errors'].append('{}.{}: at least one rule is required'.format(subnode, node)) + continue + + for ruleno in rdata['rule']: + ruledata = rdata['rule'][ruleno] + ruledefaults = defaults(base + ['authoritative-domain', rtype, 'rule']) # T2665 + ruledata = dict_merge(ruledefaults, ruledata) + flags = "" + if 'lookup-srv' in ruledata: + flags += "S" + if 'lookup-a' in ruledata: + flags += "A" + if 'resolve-uri' in ruledata: + flags += "U" + if 'protocol-specific' in ruledata: + flags += "P" + + if 'order' in ruledata: + order = ruledata['order'] + else: + order = ruleno + + if 'regexp' in ruledata: + regexp= ruledata['regexp'].replace("\"", "\\\"") + else: + regexp = '' + + if ruledata['replacement']: + replacement = '{}.'.format(ruledata['replacement']) + else: + replacement = '' + + zone['records'].append({ + 'name': subnode, + 'type': rtype.upper(), + 'ttl': rdata['ttl'], + 'value': '{} {} "{}" "{}" "{}" {}'.format(order, ruledata['preference'], flags, ruledata['service'], regexp, replacement) + }) + + dns['authoritative_zones'].append(zone) + return dns def verify(dns): @@ -86,6 +266,11 @@ def verify(dns): if 'server' not in dns['domain'][domain]: raise ConfigError(f'No server configured for domain {domain}!') + if dns['authoritative_zone_errors']: + for error in dns['authoritative_zone_errors']: + print(error) + raise ConfigError('Invalid authoritative records have been defined') + if 'system' in dns: if not ('system_name_server' in dns or 'system_name_server_dhcp' in dns): print("Warning: No 'system name-server' or 'system " \ @@ -104,6 +289,14 @@ def generate(dns): render(pdns_rec_lua_conf_file, 'dns-forwarding/recursor.conf.lua.tmpl', dns, user=pdns_rec_user, group=pdns_rec_group) + for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): + os.unlink(zone_filename) + + for zone in dns['authoritative_zones']: + render(zone['file'], 'dns-forwarding/recursor.zone.conf.tmpl', + zone, user=pdns_rec_user, group=pdns_rec_group) + + # if vyos-hostsd didn't create its files yet, create them (empty) for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: with open(file, 'a'): @@ -119,6 +312,9 @@ def apply(dns): if os.path.isfile(pdns_rec_config_file): os.unlink(pdns_rec_config_file) + + for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): + os.unlink(zone_filename) else: ### first apply vyos-hostsd config hc = hostsd_client() @@ -153,6 +349,12 @@ def apply(dns): if 'domain' in dns: hc.add_forward_zones(dns['domain']) + # hostsd generates NTAs for the authoritative zones + # the list and keys() are required as get returns a dict, not list + hc.delete_authoritative_zones(list(hc.get_authoritative_zones())) + if 'authoritative_domain' in dns: + hc.add_authoritative_zones(list(dns['authoritative_domain'].keys())) + # call hostsd to generate forward-zones and its lua-config-file hc.apply() diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index 4c4bb036e..52753faa5 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -139,6 +139,27 @@ # } # # +### authoritative_zones +## Additional zones hosted authoritatively by pdns-recursor. +## We add NTAs for these zones but do not do much else here. +# +# { 'type': 'authoritative_zones', +# 'op': 'add', +# 'data': ['', ...] +# } +# +# { 'type': 'authoritative_zones', +# 'op': 'delete', +# 'data': ['', ...] +# } +# +# { 'type': 'authoritative_zones', +# 'op': 'get', +# } +# response: +# { 'data': ['', ...] } +# +# ### search_domains # # { 'type': 'search_domains', @@ -255,6 +276,7 @@ STATE = { "name_server_tags_recursor": [], "name_server_tags_system": [], "forward_zones": {}, + "authoritative_zones": [], "hosts": {}, "host_name": "vyos", "domain_name": "", @@ -267,7 +289,8 @@ base_schema = Schema({ Required('op'): Any('add', 'delete', 'set', 'get', 'apply'), 'type': Any('name_servers', 'name_server_tags_recursor', 'name_server_tags_system', - 'forward_zones', 'search_domains', 'hosts', 'host_name'), + 'forward_zones', 'authoritative_zones' 'search_domains', + 'hosts', 'host_name'), 'data': Any(list, dict), 'tag': str, 'tag_regex': str @@ -347,6 +370,11 @@ msg_schema_map = { 'delete': data_list_schema, 'get': op_type_schema }, + 'authoritative_zones': { + 'add': data_list_schema, + 'delete': data_list_schema, + 'get': op_type_schema + }, 'search_domains': { 'add': data_dict_list_schema, 'delete': data_list_schema, @@ -522,7 +550,7 @@ def handle_message(msg): data = get_option(msg, 'data') if _type in ['name_servers', 'forward_zones', 'search_domains', 'hosts']: delete_items_from_dict(STATE[_type], data) - elif _type in ['name_server_tags_recursor', 'name_server_tags_system']: + elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'authoritative_zones']: delete_items_from_list(STATE[_type], data) else: raise ValueError(f'Operation "{op}" unknown data type "{_type}"') @@ -534,7 +562,7 @@ def handle_message(msg): elif _type in ['forward_zones', 'hosts']: add_items_to_dict(STATE[_type], data) # maybe we need to rec_control clear-nta each domain that was removed here? - elif _type in ['name_server_tags_recursor', 'name_server_tags_system']: + elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'authoritative_zones']: add_items_to_list(STATE[_type], data) else: raise ValueError(f'Operation "{op}" unknown data type "{_type}"') @@ -550,7 +578,7 @@ def handle_message(msg): if _type in ['name_servers', 'search_domains', 'hosts']: tag_regex = get_option(msg, 'tag_regex') result = get_items_from_dict_regex(STATE[_type], tag_regex) - elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'forward_zones']: + elif _type in ['name_server_tags_recursor', 'name_server_tags_system', 'forward_zones', 'authoritative_zones']: result = STATE[_type] else: raise ValueError(f'Operation "{op}" unknown data type "{_type}"') -- cgit v1.2.3 From 9386ac0e3d7f3bd3b566fa67ad42e1be98057274 Mon Sep 17 00:00:00 2001 From: Lucas Christian Date: Tue, 12 Oct 2021 22:38:06 -0700 Subject: Fix error when no domains are defined --- src/conf_mode/dns_forwarding.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index c2816589c..278fa7226 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -74,7 +74,7 @@ def get_config(config=None): dns['authoritative_zone_errors'] = [] for node in dns['authoritative_domain']: zonedata = dns['authoritative_domain'][node] - if ('disable' in zonedata) || (not 'records' in zonedata): + if ('disable' in zonedata) or (not 'records' in zonedata): continue zone = { 'name': node, @@ -266,7 +266,7 @@ def verify(dns): if 'server' not in dns['domain'][domain]: raise ConfigError(f'No server configured for domain {domain}!') - if dns['authoritative_zone_errors']: + if ('authoritative_zone_errors' in dns) and dns['authoritative_zone_errors']: for error in dns['authoritative_zone_errors']: print(error) raise ConfigError('Invalid authoritative records have been defined') @@ -292,9 +292,10 @@ def generate(dns): for zone_filename in glob(f'{pdns_rec_run_dir}/zone.*.conf'): os.unlink(zone_filename) - for zone in dns['authoritative_zones']: - render(zone['file'], 'dns-forwarding/recursor.zone.conf.tmpl', - zone, user=pdns_rec_user, group=pdns_rec_group) + if 'authoritative_zones' in dns: + for zone in dns['authoritative_zones']: + render(zone['file'], 'dns-forwarding/recursor.zone.conf.tmpl', + zone, user=pdns_rec_user, group=pdns_rec_group) # if vyos-hostsd didn't create its files yet, create them (empty) -- cgit v1.2.3 From d6a79444ff131220529938b0ff849f43f3a9e1c0 Mon Sep 17 00:00:00 2001 From: Lucas Christian Date: Tue, 12 Oct 2021 22:51:29 -0700 Subject: Fix default values --- interface-definitions/dns-forwarding.xml.in | 2 +- src/conf_mode/dns_forwarding.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index ced138bff..4faf604ad 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -360,7 +360,7 @@ - Host an DNS "SRV" record + "SRV" record text A DNS name relative to the root record diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 278fa7226..5a5ae4d14 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -94,7 +94,7 @@ def get_config(config=None): rdata = recorddata[rtype][subnode] if rtype in [ 'a', 'aaaa' ]: - rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 rdata = dict_merge(rdefaults, rdata) if not 'address' in rdata: @@ -109,7 +109,7 @@ def get_config(config=None): 'value': address }) elif rtype in ['cname', 'ptr']: - rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 rdata = dict_merge(rdefaults, rdata) if not 'target' in rdata: @@ -123,7 +123,7 @@ def get_config(config=None): 'value': '{}.'.format(rdata['target']) }) elif rtype == 'mx': - rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 del rdefaults['server'] rdata = dict_merge(rdefaults, rdata) @@ -133,7 +133,7 @@ def get_config(config=None): for servername in rdata['server']: serverdata = rdata['server'][servername] - serverdefaults = defaults(base + ['authoritative-domain', rtype, 'server']) # T2665 + serverdefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'server']) # T2665 serverdata = dict_merge(serverdefaults, serverdata) zone['records'].append({ 'name': subnode, @@ -142,7 +142,7 @@ def get_config(config=None): 'value': '{} {}.'.format(serverdata['priority'], servername) }) elif rtype == 'txt': - rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 rdata = dict_merge(rdefaults, rdata) if not 'value' in rdata: @@ -157,7 +157,7 @@ def get_config(config=None): 'value': "\"{}\"".format(value.replace("\"", "\\\"")) }) elif rtype == 'spf': - rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 rdata = dict_merge(rdefaults, rdata) if not 'value' in rdata: @@ -171,7 +171,7 @@ def get_config(config=None): 'value': '"{}"'.format(rdata['value'].replace("\"", "\\\"")) }) elif rtype == 'srv': - rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 del rdefaults['entry'] rdata = dict_merge(rdefaults, rdata) @@ -181,7 +181,7 @@ def get_config(config=None): for entryno in rdata['entry']: entrydata = rdata['entry'][entryno] - entrydefaults = defaults(base + ['authoritative-domain', rtype, 'entry']) # T2665 + entrydefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'entry']) # T2665 entrydata = dict_merge(entrydefaults, entrydata) if not 'hostname' in entrydata: @@ -199,7 +199,7 @@ def get_config(config=None): 'value': '{} {} {} {}.'.format(entrydata['priority'], entrydata['weight'], entrydata['port'], entrydata['hostname']) }) elif rtype == 'naptr': - rdefaults = defaults(base + ['authoritative-domain', rtype]) # T2665 + rdefaults = defaults(base + ['authoritative-domain', 'records', rtype]) # T2665 del rdefaults['rule'] rdata = dict_merge(rdefaults, rdata) @@ -210,7 +210,7 @@ def get_config(config=None): for ruleno in rdata['rule']: ruledata = rdata['rule'][ruleno] - ruledefaults = defaults(base + ['authoritative-domain', rtype, 'rule']) # T2665 + ruledefaults = defaults(base + ['authoritative-domain', 'records', rtype, 'rule']) # T2665 ruledata = dict_merge(ruledefaults, ruledata) flags = "" if 'lookup-srv' in ruledata: -- cgit v1.2.3 From 98704f45a4d261ae472e9b299080a1f06275ae81 Mon Sep 17 00:00:00 2001 From: Lucas Christian Date: Tue, 12 Oct 2021 22:59:52 -0700 Subject: Don't generate NTA when zone is disabled --- src/conf_mode/dns_forwarding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 5a5ae4d14..23a16df63 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -353,8 +353,8 @@ def apply(dns): # hostsd generates NTAs for the authoritative zones # the list and keys() are required as get returns a dict, not list hc.delete_authoritative_zones(list(hc.get_authoritative_zones())) - if 'authoritative_domain' in dns: - hc.add_authoritative_zones(list(dns['authoritative_domain'].keys())) + if 'authoritative_zones' in dns: + hc.add_authoritative_zones(list(map(lambda zone: zone['name'], dns['authoritative_zones']))) # call hostsd to generate forward-zones and its lua-config-file hc.apply() -- cgit v1.2.3 From 035a3f1b053136e553f9f03f536d0a3d6daabb72 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 23 Nov 2021 17:57:48 +0000 Subject: netns: T3829: Add op-mode for show netns --- op-mode-definitions/show-netns.xml.in | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 op-mode-definitions/show-netns.xml.in diff --git a/op-mode-definitions/show-netns.xml.in b/op-mode-definitions/show-netns.xml.in new file mode 100644 index 000000000..8d5072d4e --- /dev/null +++ b/op-mode-definitions/show-netns.xml.in @@ -0,0 +1,13 @@ + + + + + + + Show network namespace information + + ip netns ls + + + + -- cgit v1.2.3 From 3d413c06621c3c299972380391285b456919deb5 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 23 Nov 2021 17:58:58 +0000 Subject: netns: T3829: Allow attach dumX interfaces to netns --- interface-definitions/interfaces-dummy.xml.in | 1 + 1 file changed, 1 insertion(+) diff --git a/interface-definitions/interfaces-dummy.xml.in b/interface-definitions/interfaces-dummy.xml.in index 2bc88c1a7..4d4c44160 100644 --- a/interface-definitions/interfaces-dummy.xml.in +++ b/interface-definitions/interfaces-dummy.xml.in @@ -27,6 +27,7 @@ #include + #include #include -- cgit v1.2.3 From 502448171a62aa809c07d19a3999df885a65f715 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 23 Nov 2021 18:03:23 +0000 Subject: netns: T3829: Add netns set section in interface.py --- python/vyos/ifconfig/interface.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 58d130ef6..50da2553a 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -135,6 +135,9 @@ class Interface(Control): 'validate': assert_mtu, 'shellcmd': 'ip link set dev {ifname} mtu {value}', }, + 'netns': { + 'shellcmd': 'ip link set dev {ifname} netns {value}', + }, 'vrf': { 'convert': lambda v: f'master {v}' if v else 'nomaster', 'shellcmd': 'ip link set dev {ifname} {value}', @@ -512,6 +515,21 @@ class Interface(Control): if prev_state == 'up': self.set_admin_state('up') + def set_netns(self, netns): + """ + Add/Remove interface from given NETNS. + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('dum0').set_netns('foo') + """ + + #tmp = self.get_interface('netns') + #if tmp == netns: + # return None + + self.set_interface('netns', netns) + def set_vrf(self, vrf): """ Add/Remove interface from given VRF instance. @@ -1405,6 +1423,13 @@ class Interface(Control): # checked before self.set_vrf(config.get('vrf', '')) + # If interface attached to NETNS we shouldn't check all other settings + # As interface placed to separate logical stack + # Configure NETNS + if dict_search('netns', config) != None: + self.set_netns(config.get('netns', '')) + return + # Configure MSS value for IPv4 TCP connections tmp = dict_search('ip.adjust_mss', config) value = tmp if (tmp != None) else '0' -- cgit v1.2.3 From a2fe5dc40d439a28deb4d09e78fcf93aba4555a4 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 23 Nov 2021 18:07:12 +0000 Subject: netns: T3829: Add XML for netns CLI configuration --- .../include/interface/netns.xml.i | 14 +++++++++++++ interface-definitions/netns.xml.in | 23 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 interface-definitions/include/interface/netns.xml.i create mode 100644 interface-definitions/netns.xml.in diff --git a/interface-definitions/include/interface/netns.xml.i b/interface-definitions/include/interface/netns.xml.i new file mode 100644 index 000000000..39f9118fa --- /dev/null +++ b/interface-definitions/include/interface/netns.xml.i @@ -0,0 +1,14 @@ + + + + Network namespace name + + text + Network namespace name + + + netns name + + + + diff --git a/interface-definitions/netns.xml.in b/interface-definitions/netns.xml.in new file mode 100644 index 000000000..88451737a --- /dev/null +++ b/interface-definitions/netns.xml.in @@ -0,0 +1,23 @@ + + + + + Network namespace + 299 + + + + + Network namespace name + + + + + Network namespace description + + + + + + + -- cgit v1.2.3 From 47584e030f3341b265e826643951648982cb20d0 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Tue, 23 Nov 2021 18:11:10 +0000 Subject: netns: T3829: Ability to configure network namespaces --- interface-definitions/netns.xml.in | 10 +-- python/vyos/ifconfig/interface.py | 42 ++++++--- python/vyos/util.py | 18 ++++ smoketest/scripts/cli/test_interfaces_netns.py | 83 +++++++++++++++++ src/conf_mode/netns.py | 118 +++++++++++++++++++++++++ 5 files changed, 254 insertions(+), 17 deletions(-) create mode 100755 smoketest/scripts/cli/test_interfaces_netns.py create mode 100755 src/conf_mode/netns.py diff --git a/interface-definitions/netns.xml.in b/interface-definitions/netns.xml.in index 88451737a..80de805fb 100644 --- a/interface-definitions/netns.xml.in +++ b/interface-definitions/netns.xml.in @@ -9,13 +9,13 @@ Network namespace name + + ^[a-zA-Z0-9-_]{1,100} + + Netns name must be alphanumeric and can contain hyphens and underscores. - - - Network namespace description - - + #include diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 50da2553a..bcb692697 100755 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -37,6 +37,7 @@ from vyos.util import mac2eui64 from vyos.util import dict_search from vyos.util import read_file from vyos.util import get_interface_config +from vyos.util import get_interface_namespace from vyos.util import is_systemd_service_active from vyos.template import is_ipv4 from vyos.template import is_ipv6 @@ -515,19 +516,33 @@ class Interface(Control): if prev_state == 'up': self.set_admin_state('up') + def del_netns(self, netns): + """ + Remove interface from given NETNS. + """ + + # If NETNS does not exist then there is nothing to delete + if not os.path.exists(f'/run/netns/{netns}'): + return None + + # As a PoC we only allow 'dummy' interfaces + if 'dum' not in self.ifname: + return None + + # Check if interface realy exists in namespace + if get_interface_namespace(self.ifname) != None: + self._cmd(f'ip netns exec {get_interface_namespace(self.ifname)} ip link del dev {self.ifname}') + return + def set_netns(self, netns): """ - Add/Remove interface from given NETNS. + Add interface from given NETNS. Example: >>> from vyos.ifconfig import Interface >>> Interface('dum0').set_netns('foo') """ - #tmp = self.get_interface('netns') - #if tmp == netns: - # return None - self.set_interface('netns', netns) def set_vrf(self, vrf): @@ -1371,6 +1386,16 @@ class Interface(Control): if mac: self.set_mac(mac) + # If interface is connected to NETNS we don't have to check all other + # settings like MTU/IPv6/sysctl values, etc. + # Since the interface is pushed onto a separate logical stack + # Configure NETNS + if dict_search('netns', config) != None: + self.set_netns(config.get('netns', '')) + return + else: + self.del_netns(config.get('netns', '')) + # Update interface description self.set_alias(config.get('description', '')) @@ -1423,13 +1448,6 @@ class Interface(Control): # checked before self.set_vrf(config.get('vrf', '')) - # If interface attached to NETNS we shouldn't check all other settings - # As interface placed to separate logical stack - # Configure NETNS - if dict_search('netns', config) != None: - self.set_netns(config.get('netns', '')) - return - # Configure MSS value for IPv4 TCP connections tmp = dict_search('ip.adjust_mss', config) value = tmp if (tmp != None) else '0' diff --git a/python/vyos/util.py b/python/vyos/util.py index 9aa1f98d2..6c375e1a6 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -794,6 +794,24 @@ def get_interface_address(interface): tmp = loads(cmd(f'ip -d -j addr show {interface}'))[0] return tmp +def get_interface_namespace(iface): + """ + Returns wich netns the interface belongs to + """ + from json import loads + # Check if netns exist + tmp = loads(cmd(f'ip --json netns ls')) + if len(tmp) == 0: + return None + + for ns in tmp: + namespace = f'{ns["name"]}' + # Search interface in each netns + data = loads(cmd(f'ip netns exec {namespace} ip -j link show')) + for compare in data: + if iface == compare["ifname"]: + return namespace + def get_all_vrfs(): """ Return a dictionary of all system wide known VRF instances """ from json import loads diff --git a/smoketest/scripts/cli/test_interfaces_netns.py b/smoketest/scripts/cli/test_interfaces_netns.py new file mode 100755 index 000000000..9975a6b09 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_netns.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 re +import os +import json +import unittest + +from netifaces import interfaces +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Interface +from vyos.ifconfig import Section +from vyos.util import cmd + +base_path = ['netns'] +namespaces = ['mgmt', 'front', 'back', 'ams-ix'] + +class NETNSTest(VyOSUnitTestSHIM.TestCase): + + def setUp(self): + self._interfaces = ['dum10', 'dum12', 'dum50'] + + def test_create_netns(self): + for netns in namespaces: + base = base_path + ['name', netns] + self.cli_set(base) + + # commit changes + self.cli_commit() + + netns_list = cmd('ip netns ls') + + # Verify NETNS configuration + for netns in namespaces: + self.assertTrue(netns in netns_list) + + + def test_netns_assign_interface(self): + netns = 'foo' + self.cli_set(['netns', 'name', netns]) + + # Set + for iface in self._interfaces: + self.cli_set(['interfaces', 'dummy', iface, 'netns', netns]) + + # commit changes + self.cli_commit() + + netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show') + + for iface in self._interfaces: + self.assertTrue(iface in netns_iface_list) + + # Delete + for iface in self._interfaces: + self.cli_delete(['interfaces', 'dummy', iface, 'netns', netns]) + + # commit changes + self.cli_commit() + + netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show') + + for iface in self._interfaces: + self.assertNotIn(iface, netns_iface_list) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/src/conf_mode/netns.py b/src/conf_mode/netns.py new file mode 100755 index 000000000..0924eb616 --- /dev/null +++ b/src/conf_mode/netns.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os + +from sys import exit +from tempfile import NamedTemporaryFile + +from vyos.config import Config +from vyos.configdict import node_changed +from vyos.ifconfig import Interface +from vyos.util import call +from vyos.util import dict_search +from vyos.util import get_interface_config +from vyos import ConfigError +from vyos import airbag +airbag.enable() + + +def netns_interfaces(c, match): + """ + get NETNS bound interfaces + """ + matched = [] + old_level = c.get_level() + c.set_level(['interfaces']) + section = c.get_config_dict([], get_first_key=True) + for type in section: + interfaces = section[type] + for name in interfaces: + interface = interfaces[name] + if 'netns' in interface: + v = interface.get('netns', '') + if v == match: + matched.append(name) + + c.set_level(old_level) + return matched + +def get_config(config=None): + if config: + conf = config + else: + conf = Config() + + base = ['netns'] + netns = conf.get_config_dict(base, get_first_key=True, + no_tag_node_value_mangle=True) + + # determine which NETNS has been removed + for name in node_changed(conf, base + ['name']): + if 'netns_remove' not in netns: + netns.update({'netns_remove' : {}}) + + netns['netns_remove'][name] = {} + # get NETNS bound interfaces + interfaces = netns_interfaces(conf, name) + if interfaces: netns['netns_remove'][name]['interface'] = interfaces + + return netns + +def verify(netns): + # ensure NETNS is not assigned to any interface + if 'netns_remove' in netns: + for name, config in netns['netns_remove'].items(): + if 'interface' in config: + raise ConfigError(f'Can not remove NETNS "{name}", it still has '\ + f'member interfaces!') + + if 'name' in netns: + for name, config in netns['name'].items(): + print(name) + + return None + + +def generate(netns): + if not netns: + return None + + return None + + +def apply(netns): + + for tmp in (dict_search('netns_remove', netns) or []): + if os.path.isfile(f'/run/netns/{tmp}'): + call(f'ip netns del {tmp}') + + if 'name' in netns: + for name, config in netns['name'].items(): + if not os.path.isfile(f'/run/netns/{name}'): + call(f'ip netns add {name}') + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) -- cgit v1.2.3 From c5aac0a6a1aba39b734a7b0f65ee4282fc72a605 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Fri, 3 Dec 2021 00:32:39 +0700 Subject: T4035: correct the interface basename extraction logic to avoid confusing 'v' in GENEVE interface prefix ('gnv') with a "vXXX" part of a VRRP interface --- python/vyos/ifconfig/section.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 0e4447b9e..91f667b65 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -52,12 +52,12 @@ class Section: name: name of the interface vlan: if vlan is True, do not stop at the vlan number """ - name = name.rstrip('0123456789') - name = name.rstrip('.') - if vlan: - name = name.rstrip('0123456789.') if vrrp: - name = name.rstrip('0123456789v') + name = re.sub(r'\d(\d|v|\.)*$', '', name) + elif vlan: + name = re.sub(r'\d(\d|\.)*$', '', name) + else: + name = re.sub(r'\d+$', '', name) return name @classmethod -- cgit v1.2.3 From ae16a51506cdc4de8e18f4678b8f55654d4abbba Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Thu, 2 Dec 2021 18:13:29 -0600 Subject: configquery: T3402: use vyatta-op-cmd-wrapper to provide environment --- python/vyos/configquery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/vyos/configquery.py b/python/vyos/configquery.py index 1cdcbcf39..b981463e4 100644 --- a/python/vyos/configquery.py +++ b/python/vyos/configquery.py @@ -110,7 +110,7 @@ class VbashOpRun(GenericOpRun): def run(self, path: list, **kwargs): cmd = ' '.join(path) - (out, err) = vyos.util.popen(f'. /opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-op-run; _vyatta_op_run {cmd}', stderr=STDOUT, **kwargs) + (out, err) = vyos.util.popen(f'/opt/vyatta/bin/vyatta-op-cmd-wrapper {cmd}', stderr=STDOUT, **kwargs) if err: raise ConfigQueryError(out) return out -- cgit v1.2.3 From d0c62f2e3f8d7a89b3502a400a88933b2d0b4501 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Sat, 4 Dec 2021 00:19:00 -0500 Subject: T4035: add op mode "show interfaces geneve" commands --- op-mode-definitions/show-interfaces-geneve.xml.in | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 op-mode-definitions/show-interfaces-geneve.xml.in diff --git a/op-mode-definitions/show-interfaces-geneve.xml.in b/op-mode-definitions/show-interfaces-geneve.xml.in new file mode 100644 index 000000000..a47933315 --- /dev/null +++ b/op-mode-definitions/show-interfaces-geneve.xml.in @@ -0,0 +1,42 @@ + + + + + + + + + Show specified GENEVE interface information + + interfaces geneve + + + ${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" + + + + Show summary of the specified GENEVE interface information + + ${vyos_op_scripts_dir}/show_interfaces.py --intf="$4" --action=show-brief + + + + + + Show GENEVE interface information + + ${vyos_op_scripts_dir}/show_interfaces.py --intf-type=geneve --action=show-brief + + + + Show detailed GENEVE interface information + + ${vyos_op_scripts_dir}/show_interfaces.py --intf-type=geneve --action=show + + + + + + + + -- cgit v1.2.3 From 2b8d9131d6c32b52b4dde42e663ac8518d955852 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 07:18:26 +0100 Subject: xml: bfd: hint default values on CLI tab complete --- interface-definitions/include/bfd-common.xml.i | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface-definitions/include/bfd-common.xml.i b/interface-definitions/include/bfd-common.xml.i index 1d6ab5d55..6021576f6 100644 --- a/interface-definitions/include/bfd-common.xml.i +++ b/interface-definitions/include/bfd-common.xml.i @@ -15,7 +15,7 @@ Minimum interval of receiving control packets u32:10-60000 - Interval in milliseconds + Interval in milliseconds (default: 300) @@ -28,7 +28,7 @@ Minimum interval of transmitting control packets u32:10-60000 - Interval in milliseconds + Interval in milliseconds (default: 300) @@ -41,7 +41,7 @@ Multiplier to determine packet loss u32:2-255 - Remote transmission interval will be multiplied by this value + Remote transmission interval will be multiplied by this value (default: 3) -- cgit v1.2.3 From 86df16c3b3055b82f3e0a9e705794302fa2df257 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 07:21:24 +0100 Subject: bfd: T3310: add key_mangling option when calling get_config_dict() --- data/templates/frr/bfdd.frr.tmpl | 17 +++++++++-------- src/conf_mode/protocols_bfd.py | 3 ++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/data/templates/frr/bfdd.frr.tmpl b/data/templates/frr/bfdd.frr.tmpl index c14939677..dd26b930a 100644 --- a/data/templates/frr/bfdd.frr.tmpl +++ b/data/templates/frr/bfdd.frr.tmpl @@ -6,11 +6,11 @@ bfd detect-multiplier {{ profile_config.interval.multiplier }} receive-interval {{ profile_config.interval.receive }} transmit-interval {{ profile_config.interval.transmit }} -{% if profile_config.interval['echo-interval'] is defined and profile_config.interval['echo-interval'] is not none %} - echo transmit-interval {{ profile_config.interval['echo-interval'] }} - echo receive-interval {{ profile_config.interval['echo-interval'] }} +{% if profile_config.interval.echo_interval is defined and profile_config.interval.echo_interval is not none %} + echo transmit-interval {{ profile_config.interval.echo_interval }} + echo receive-interval {{ profile_config.interval.echo_interval }} {% endif %} -{% if profile_config['echo-mode'] is defined %} +{% if profile_config.echo_mode is defined %} echo-mode {% endif %} {% if profile_config.shutdown is defined %} @@ -24,14 +24,15 @@ bfd {% endif %} {% if peer is defined and peer is not none %} {% for peer_name, peer_config in peer.items() %} - peer {{ peer_name }}{{ ' multihop' if peer_config.multihop is defined }}{{ ' local-address ' + peer_config.source.address if peer_config.source is defined and peer_config.source.address is defined }}{{ ' interface ' + peer_config.source.interface if peer_config.source is defined and peer_config.source.interface is defined }} + peer {{ peer_name }}{{ ' multihop' if peer_config.multihop is defined }}{{ ' local-address ' + peer_config.source.address if peer_config.source is defined and peer_config.source.address is defined }}{{ ' interface ' + peer_config.source.interface if peer_config.source is defined and peer_config.source.interface is defined }} {{ ' vrf ' + peer_config.vrf if peer_config.vrf is defined and peer_config.vrf is not none }} detect-multiplier {{ peer_config.interval.multiplier }} receive-interval {{ peer_config.interval.receive }} transmit-interval {{ peer_config.interval.transmit }} -{% if peer_config.interval['echo-interval'] is defined and peer_config.interval['echo-interval'] is not none %} - echo-interval {{ peer_config.interval['echo-interval'] }} +{% if peer_config.interval.echo_interval is defined and peer_config.interval.echo_interval is not none %} + echo transmit-interval {{ peer_config.interval.echo_interval }} + echo receive-interval {{ peer_config.interval.echo_interval }} {% endif %} -{% if peer_config['echo-mode'] is defined %} +{% if peer_config.echo_mode is defined %} echo-mode {% endif %} {% if peer_config.shutdown is defined %} diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 94825ba10..1f8e4ddc6 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -33,7 +33,8 @@ def get_config(config=None): else: conf = Config() base = ['protocols', 'bfd'] - bfd = conf.get_config_dict(base, get_first_key=True) + bfd = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True) # Bail out early if configuration tree does not exist if not conf.exists(base): return bfd -- cgit v1.2.3 From 63cadf52a4b8d8aa31a868c4b19e44a9eff12d37 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 07:23:14 +0100 Subject: bfd: T4044: add VRF support for peers --- interface-definitions/protocols-bfd.xml.in | 1 + smoketest/scripts/cli/test_protocols_bfd.py | 11 ++++++++++- src/conf_mode/protocols_bfd.py | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/interface-definitions/protocols-bfd.xml.in b/interface-definitions/protocols-bfd.xml.in index 7b22b8125..d5a968001 100644 --- a/interface-definitions/protocols-bfd.xml.in +++ b/interface-definitions/protocols-bfd.xml.in @@ -73,6 +73,7 @@ + #include diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py index 46a019dfc..532814ef3 100755 --- a/smoketest/scripts/cli/test_protocols_bfd.py +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -24,6 +24,7 @@ PROCESS_NAME = 'bfdd' base_path = ['protocols', 'bfd'] dum_if = 'dum1001' +vrf_name = 'red' peers = { '192.0.2.10' : { 'intv_rx' : '500', @@ -42,7 +43,7 @@ peers = { }, '2001:db8::a' : { 'source_addr': '2001:db8::1', - 'source_intf': dum_if, + 'vrf' : vrf_name, }, '2001:db8::b' : { 'source_addr': '2001:db8::1', @@ -73,6 +74,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.assertTrue(process_named_running(PROCESS_NAME)) def test_bfd_peer(self): + self.cli_set(['vrf', 'name', vrf_name, 'table', '1000']) + for peer, peer_config in peers.items(): if 'echo_mode' in peer_config: self.cli_set(base_path + ['peer', peer, 'echo-mode']) @@ -92,6 +95,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['peer', peer, 'source', 'address', peer_config["source_addr"]]) if 'source_intf' in peer_config: self.cli_set(base_path + ['peer', peer, 'source', 'interface', peer_config["source_intf"]]) + if 'vrf' in peer_config: + self.cli_set(base_path + ['peer', peer, 'vrf', peer_config["vrf"]]) # commit changes self.cli_commit() @@ -106,6 +111,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): tmp += f' local-address {peer_config["source_addr"]}' if 'source_intf' in peer_config: tmp += f' interface {peer_config["source_intf"]}' + if 'vrf' in peer_config: + tmp += f' vrf {peer_config["vrf"]}' self.assertIn(tmp, frrconfig) peerconfig = self.getFRRconfig(f' peer {peer}', end='') @@ -126,6 +133,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): else: self.assertNotIn(f'shutdown', peerconfig) + self.cli_delete(['vrf', 'name', vrf_name]) + def test_bfd_profile(self): peer = '192.0.2.10' diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 1f8e4ddc6..6981d0db1 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -18,6 +18,7 @@ import os from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configverify import verify_vrf from vyos.template import is_ipv6 from vyos.template import render_to_string from vyos.validate import is_ipv6_link_local @@ -83,6 +84,9 @@ def verify(bfd): if 'source' in peer_config and 'interface' in peer_config['source']: raise ConfigError('Multihop and source interface cannot be used together') + if 'vrf' in peer_config: + verify_vrf(peer_config) + return None def generate(bfd): -- cgit v1.2.3 From 9ad20a63b3f5557ea03e8778f29a173bb5f56cef Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 07:28:26 +0100 Subject: bfd: T4043: add support for passive mode Mark session as passive: a passive session will not attempt to start the connection and will wait for control packets from peer before it begins replying. This feature is useful when you have a router that acts as the central node of a star network and you want to avoid sending BFD control packets you don't need to. The default is active-mode --- data/templates/frr/bfdd.frr.tmpl | 6 ++++++ interface-definitions/include/bfd-common.xml.i | 6 ++++++ smoketest/scripts/cli/test_protocols_bfd.py | 10 ++++++++++ 3 files changed, 22 insertions(+) diff --git a/data/templates/frr/bfdd.frr.tmpl b/data/templates/frr/bfdd.frr.tmpl index dd26b930a..e0e94c24d 100644 --- a/data/templates/frr/bfdd.frr.tmpl +++ b/data/templates/frr/bfdd.frr.tmpl @@ -13,6 +13,9 @@ bfd {% if profile_config.echo_mode is defined %} echo-mode {% endif %} +{% if profile_config.passive is defined %} + passive-mode +{% endif %} {% if profile_config.shutdown is defined %} shutdown {% else %} @@ -35,6 +38,9 @@ bfd {% if peer_config.echo_mode is defined %} echo-mode {% endif %} +{% if peer_config.passive is defined %} + passive-mode +{% endif %} {% if peer_config.shutdown is defined %} shutdown {% else %} diff --git a/interface-definitions/include/bfd-common.xml.i b/interface-definitions/include/bfd-common.xml.i index 6021576f6..8379784f7 100644 --- a/interface-definitions/include/bfd-common.xml.i +++ b/interface-definitions/include/bfd-common.xml.i @@ -63,6 +63,12 @@ + + + Do not attempt to start sessions + + + Disable this peer diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py index 532814ef3..d33a64301 100755 --- a/smoketest/scripts/cli/test_protocols_bfd.py +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -38,6 +38,7 @@ peers = { 'intv_mult' : '100', 'intv_rx' : '222', 'intv_tx' : '333', + 'passive' : '', 'shutdown' : '', 'source_intf': dum_if, }, @@ -63,6 +64,7 @@ profiles = { 'bar' : { 'intv_mult' : '102', 'intv_rx' : '444', + 'passive' : '', }, } @@ -89,6 +91,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['peer', peer, 'interval', 'transmit', peer_config["intv_tx"]]) if 'multihop' in peer_config: self.cli_set(base_path + ['peer', peer, 'multihop']) + if 'passive' in peer_config: + self.cli_set(base_path + ['peer', peer, 'passive']) if 'shutdown' in peer_config: self.cli_set(base_path + ['peer', peer, 'shutdown']) if 'source_addr' in peer_config: @@ -128,6 +132,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.assertIn(f'receive-interval {peer_config["intv_rx"]}', peerconfig) if 'intv_tx' in peer_config: self.assertIn(f'transmit-interval {peer_config["intv_tx"]}', peerconfig) + if 'passive' in peer_config: + self.assertIn(f'passive-mode', peerconfig) if 'shutdown' in peer_config: self.assertIn(f'shutdown', peerconfig) else: @@ -149,6 +155,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['profile', profile, 'interval', 'receive', profile_config["intv_rx"]]) if 'intv_tx' in profile_config: self.cli_set(base_path + ['profile', profile, 'interval', 'transmit', profile_config["intv_tx"]]) + if 'passive' in profile_config: + self.cli_set(base_path + ['profile', profile, 'passive']) if 'shutdown' in profile_config: self.cli_set(base_path + ['profile', profile, 'shutdown']) @@ -171,6 +179,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.assertIn(f'receive-interval {profile_config["intv_rx"]}', config) if 'intv_tx' in profile_config: self.assertIn(f'transmit-interval {profile_config["intv_tx"]}', config) + if 'passive' in profile_config: + self.assertIn(f'passive-mode', config) if 'shutdown' in profile_config: self.assertIn(f'shutdown', config) else: -- cgit v1.2.3 From 467eb1f18ec971d6d3913d9ecc58b241db104f0d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 07:33:53 +0100 Subject: op-mode: disk: T4045: cleanup code to use python f'ormat strings --- op-mode-definitions/disks.xml.in | 2 +- src/op_mode/format_disk.py | 67 +++++++++++++++++----------------------- 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/op-mode-definitions/disks.xml.in b/op-mode-definitions/disks.xml.in index 2102a2e8e..117ac5065 100644 --- a/op-mode-definitions/disks.xml.in +++ b/op-mode-definitions/disks.xml.in @@ -20,7 +20,7 @@ - ${vyos_op_scripts_dir}/format_disk.py --target $3 --proto $5 + sudo ${vyos_op_scripts_dir}/format_disk.py --target $3 --proto $5 diff --git a/src/op_mode/format_disk.py b/src/op_mode/format_disk.py index df4486bce..b68948e40 100755 --- a/src/op_mode/format_disk.py +++ b/src/op_mode/format_disk.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019 VyOS maintainers and contributors +# Copyright (C) 2019-2021 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 @@ -17,11 +17,10 @@ import argparse import os import re -import sys + from datetime import datetime -from time import sleep -from vyos.util import is_admin, ask_yes_no +from vyos.util import ask_yes_no from vyos.util import call from vyos.util import cmd from vyos.util import DEVNULL @@ -38,7 +37,7 @@ def list_disks(): def is_busy(disk: str): """Check if given disk device is busy by re-reading it's partition table""" - return call(f'sudo blockdev --rereadpt /dev/{disk}', stderr=DEVNULL) != 0 + return call(f'blockdev --rereadpt /dev/{disk}', stderr=DEVNULL) != 0 def backup_partitions(disk: str): @@ -65,11 +64,11 @@ def list_partitions(disk: str): def delete_partition(disk: str, partition_idx: int): - cmd(f'sudo /sbin/parted /dev/{disk} rm {partition_idx}') + cmd(f'parted /dev/{disk} rm {partition_idx}') def format_disk_like(target: str, proto: str): - cmd(f'sudo /sbin/sfdisk -d /dev/{proto} | sudo /sbin/sfdisk --force /dev/{target}') + cmd(f'sfdisk -d /dev/{proto} | sfdisk --force /dev/{target}') if __name__ == '__main__': @@ -79,10 +78,6 @@ if __name__ == '__main__': group.add_argument('-p', '--proto', type=str, required=True, help='Prototype device to use as reference') args = parser.parse_args() - if not is_admin(): - print('Must be admin or root to format disk') - sys.exit(1) - target_disk = args.target eligible_target_disks = list_disks() @@ -90,54 +85,48 @@ if __name__ == '__main__': eligible_proto_disks = eligible_target_disks.copy() eligible_proto_disks.remove(target_disk) - fmt = { - 'target_disk': target_disk, - 'proto_disk': proto_disk, - } - if proto_disk == target_disk: print('The two disk drives must be different.') - sys.exit(1) + exit(1) - if not os.path.exists('/dev/' + proto_disk): - print('Device /dev/{proto_disk} does not exist'.format_map(fmt)) - sys.exit(1) + if not os.path.exists(f'/dev/{proto_disk}'): + print(f'Device /dev/{proto_disk} does not exist') + exit(1) if not os.path.exists('/dev/' + target_disk): - print('Device /dev/{target_disk} does not exist'.format_map(fmt)) - sys.exit(1) + print(f'Device /dev/{target_disk} does not exist') + exit(1) if target_disk not in eligible_target_disks: - print('Device {target_disk} can not be formatted'.format_map(fmt)) - sys.exit(1) + print(f'Device {target_disk} can not be formatted') + exit(1) if proto_disk not in eligible_proto_disks: - print('Device {proto_disk} can not be used as a prototype for {target_disk}'.format_map(fmt)) - sys.exit(1) + print(f'Device {proto_disk} can not be used as a prototype for {target_disk}') + exit(1) if is_busy(target_disk): - print("Disk device {target_disk} is busy. Can't format it now".format_map(fmt)) - sys.exit(1) + print(f'Disk device {target_disk} is busy, unable to format') + exit(1) - print('This will re-format disk {target_disk} so that it has the same disk\n' - 'partion sizes and offsets as {proto_disk}. This will not copy\n' - 'data from {proto_disk} to {target_disk}. But this will erase all\n' - 'data on {target_disk}.\n'.format_map(fmt)) + print(f'\nThis will re-format disk {target_disk} so that it has the same disk' + f'\npartion sizes and offsets as {proto_disk}. This will not copy' + f'\ndata from {proto_disk} to {target_disk}. But this will erase all' + f'\ndata on {target_disk}.\n') - if not ask_yes_no("Do you wish to proceed?"): - print('OK. Disk drive {target_disk} will not be re-formated'.format_map(fmt)) - sys.exit(0) + if not ask_yes_no('Do you wish to proceed?'): + print(f'Disk drive {target_disk} will not be re-formated') + exit(0) - print('OK. Re-formating disk drive {target_disk}...'.format_map(fmt)) + print(f'Re-formating disk drive {target_disk}...') print('Making backup copy of partitions...') backup_partitions(target_disk) - sleep(1) print('Deleting old partitions...') for p in list_partitions(target_disk): delete_partition(disk=target_disk, partition_idx=p) - print('Creating new partitions on {target_disk} based on {proto_disk}...'.format_map(fmt)) + print(f'Creating new partitions on {target_disk} based on {proto_disk}...') format_disk_like(target=target_disk, proto=proto_disk) - print('Done.') + print('Done!') -- cgit v1.2.3 From 4207b4c264312fc496722874ec52e2db834dec37 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 07:34:23 +0100 Subject: op-mode: disk: T4045: bugfix "format disk like " --- src/op_mode/format_disk.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/op_mode/format_disk.py b/src/op_mode/format_disk.py index b68948e40..b3ba44e87 100755 --- a/src/op_mode/format_disk.py +++ b/src/op_mode/format_disk.py @@ -43,10 +43,11 @@ def is_busy(disk: str): def backup_partitions(disk: str): """Save sfdisk partitions output to a backup file""" - device_path = '/dev/' + disk - backup_ts = datetime.now().strftime('%Y-%m-%d-%H:%M') - backup_file = '/var/tmp/backup_{}.{}'.format(disk, backup_ts) - cmd(f'sudo /sbin/sfdisk -d {device_path} > {backup_file}') + device_path = f'/dev/{disk}' + backup_ts = datetime.now().strftime('%Y%m%d-%H%M') + backup_file = f'/var/tmp/backup_{disk}.{backup_ts}' + call(f'sfdisk -d {device_path} > {backup_file}') + print(f'Partition table backup saved to {backup_file}') def list_partitions(disk: str): -- cgit v1.2.3 From 1edacb9c1788db06de570d25e274a7a7df8e81e8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 07:35:53 +0100 Subject: xml: op-mode: add "show bfd" alias node to "show protocols bfd" --- op-mode-definitions/include/bfd-common.xml.i | 54 ++++++++++++++++++++++++++++ op-mode-definitions/show-bfd.xml.in | 8 +++++ op-mode-definitions/show-protocols.xml.in | 45 +---------------------- 3 files changed, 63 insertions(+), 44 deletions(-) create mode 100644 op-mode-definitions/include/bfd-common.xml.i create mode 100644 op-mode-definitions/show-bfd.xml.in diff --git a/op-mode-definitions/include/bfd-common.xml.i b/op-mode-definitions/include/bfd-common.xml.i new file mode 100644 index 000000000..eebfbdad4 --- /dev/null +++ b/op-mode-definitions/include/bfd-common.xml.i @@ -0,0 +1,54 @@ + + + + Show Bidirectional Forwarding Detection (BFD) + + + + + Show all Bidirectional Forwarding Detection (BFD) peer status + + vtysh -c "show bfd peers" + + + + Show Bidirectional Forwarding Detection (BFD) peer counters + + vtysh -c "show bfd peers counters" + + + + + + Show Bidirectional Forwarding Detection (BFD) peer status + + + + + vtysh -c "show bfd peers" | awk -v BFD_PEER=$5 'BEGIN { regex = sprintf("(peer %s.*)vrf", BFD_PEER) } { if (match($0, regex, bfd_peer_value)) peer=bfd_peer_value[1] } END { if (peer) system("vtysh -c \"show bfd " peer "\"") }' + + + + Show Bidirectional Forwarding Detection (BFD) peer counters + + vtysh -c "show bfd peers" | awk -v BFD_PEER=$5 'BEGIN { regex = sprintf("(peer %s.*)vrf", BFD_PEER) } { if (match($0, regex, bfd_peer_value)) peer=bfd_peer_value[1] } END { if (peer) system("vtysh -c \"show bfd " peer " counters\"") }' + + + + + + Show Bidirectional Forwarding Detection peers + + vtysh -c "show bfd peers" + + + + Show Bidirectional Forwarding Detection (BFD) peers brief + + vtysh -c "show bfd peers brief" + + + + + + diff --git a/op-mode-definitions/show-bfd.xml.in b/op-mode-definitions/show-bfd.xml.in new file mode 100644 index 000000000..7339c92a2 --- /dev/null +++ b/op-mode-definitions/show-bfd.xml.in @@ -0,0 +1,8 @@ + + + + + #include + + + diff --git a/op-mode-definitions/show-protocols.xml.in b/op-mode-definitions/show-protocols.xml.in index d595e2c3c..48bfa10dc 100644 --- a/op-mode-definitions/show-protocols.xml.in +++ b/op-mode-definitions/show-protocols.xml.in @@ -7,50 +7,7 @@ Show protocol specific information - - - Show Bidirectional Forwarding Detection (BFD) - - - - - Show all Bidirectional Forwarding Detection (BFD) peer status - - vtysh -c "show bfd peers" - - - - Show Bidirectional Forwarding Detection (BFD) peer counters - - vtysh -c "show bfd peers counters" - - - - - - Show Bidirectional Forwarding Detection (BFD) peer status - - - - - vtysh -c "show bfd peers" | awk -v BFD_PEER=$5 'BEGIN { regex = sprintf("(peer %s.*)vrf", BFD_PEER) } { if (match($0, regex, bfd_peer_value)) peer=bfd_peer_value[1] } END { if (peer) system("vtysh -c \"show bfd " peer "\"") }' - - - - Show Bidirectional Forwarding Detection (BFD) peer counters - - vtysh -c "show bfd peers" | awk -v BFD_PEER=$5 'BEGIN { regex = sprintf("(peer %s.*)vrf", BFD_PEER) } { if (match($0, regex, bfd_peer_value)) peer=bfd_peer_value[1] } END { if (peer) system("vtysh -c \"show bfd " peer " counters\"") }' - - - - - - Show Bidirectional Forwarding Detection (BFD) peers brief - - vtysh -c "show bfd peers brief" - - - + #include Show static protocol parameters -- cgit v1.2.3 From 4683223c8ffcb10470f7a8a2eb48d57773ac73df Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 07:36:39 +0100 Subject: wwan: T3795: move implementation to VbashOpRun() After commit ae16a51506c ("configquery: T3402: use vyatta-op-cmd-wrapper to provide environment") we can now call VyOS op-mode commands from arbitrary Python scripts. --- src/helpers/vyos-check-wwan.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/helpers/vyos-check-wwan.py b/src/helpers/vyos-check-wwan.py index c6e6c54b7..2ff9a574f 100755 --- a/src/helpers/vyos-check-wwan.py +++ b/src/helpers/vyos-check-wwan.py @@ -18,7 +18,6 @@ from vyos.configquery import VbashOpRun from vyos.configquery import ConfigTreeQuery from vyos.util import is_wwan_connected -from vyos.util import call conf = ConfigTreeQuery() dict = conf.get_config_dict(['interfaces', 'wwan'], key_mangling=('-', '_'), @@ -30,8 +29,7 @@ for interface, interface_config in dict.items(): # do not restart this interface as it's disabled by the user continue - #op = VbashOpRun() - #op.run(['connect', 'interface', interface]) - call(f'VYOS_TAGNODE_VALUE={interface} /usr/libexec/vyos/conf_mode/interfaces-wwan.py') + op = VbashOpRun() + op.run(['connect', 'interface', interface]) exit(0) -- cgit v1.2.3 From 84b29c7c71af6663ef26dc40475cf4b0e5b179a6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 14:15:37 +0100 Subject: validators: T4042: rename bgp-route-target -> bgp-rd-rt --- .../include/bgp/afi-l2vpn-common.xml.i | 6 +-- .../include/bgp/afi-route-target-vpn.xml.i | 6 +-- src/validators/bgp-rd-rt | 51 ++++++++++++++++++++++ src/validators/bgp-route-target | 51 ---------------------- 4 files changed, 57 insertions(+), 57 deletions(-) create mode 100755 src/validators/bgp-rd-rt delete mode 100755 src/validators/bgp-route-target diff --git a/interface-definitions/include/bgp/afi-l2vpn-common.xml.i b/interface-definitions/include/bgp/afi-l2vpn-common.xml.i index 8deb189ab..d586635c8 100644 --- a/interface-definitions/include/bgp/afi-l2vpn-common.xml.i +++ b/interface-definitions/include/bgp/afi-l2vpn-common.xml.i @@ -25,7 +25,7 @@ Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN) - + @@ -37,7 +37,7 @@ Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN) - + @@ -49,7 +49,7 @@ Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN) - + diff --git a/interface-definitions/include/bgp/afi-route-target-vpn.xml.i b/interface-definitions/include/bgp/afi-route-target-vpn.xml.i index 0cd0fdd76..5784f9eac 100644 --- a/interface-definitions/include/bgp/afi-route-target-vpn.xml.i +++ b/interface-definitions/include/bgp/afi-route-target-vpn.xml.i @@ -17,7 +17,7 @@ Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN) - + @@ -29,7 +29,7 @@ Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN) - + @@ -41,7 +41,7 @@ Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN) - + diff --git a/src/validators/bgp-rd-rt b/src/validators/bgp-rd-rt new file mode 100755 index 000000000..24b0043f9 --- /dev/null +++ b/src/validators/bgp-rd-rt @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 . + +from argparse import ArgumentParser +from vyos.template import is_ipv4 + +parser = ArgumentParser() +group = parser.add_mutually_exclusive_group() +group.add_argument('--route-target', action='store', help='Validate one BGP route-target') +group.add_argument('--route-target-multi', action='store', help='Validate multiple, whitespace separated BGP route-targets') +args = parser.parse_args() + +def is_valid_rt(rt): + # every route target needs to have a colon and must consists of two parts + value = rt.split(':') + if len(value) != 2: + return False + # A route target must either be only numbers, or the first part must be an + # IPv4 address + if (is_ipv4(value[0]) or value[0].isdigit()) and value[1].isdigit(): + return True + return False + +if __name__ == '__main__': + if args.route_target: + if not is_valid_rt(args.route_target): + exit(1) + + elif args.route_target_multi: + for rt in args.route_target_multi.split(' '): + if not is_valid_rt(rt): + exit(1) + + else: + parser.print_help() + exit(1) + + exit(0) diff --git a/src/validators/bgp-route-target b/src/validators/bgp-route-target deleted file mode 100755 index e7e4d403f..000000000 --- a/src/validators/bgp-route-target +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2021 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 . - -from argparse import ArgumentParser -from vyos.template import is_ipv4 - -parser = ArgumentParser() -group = parser.add_mutually_exclusive_group() -group.add_argument('--single', action='store', help='Validate and allow only one route-target') -group.add_argument('--multi', action='store', help='Validate multiple, whitespace separated route-targets') -args = parser.parse_args() - -def is_valid_rt(rt): - # every route target needs to have a colon and must consists of two parts - value = rt.split(':') - if len(value) != 2: - return False - # A route target must either be only numbers, or the first part must be an - # IPv4 address - if (is_ipv4(value[0]) or value[0].isdigit()) and value[1].isdigit(): - return True - return False - -if __name__ == '__main__': - if args.single: - if not is_valid_rt(args.single): - exit(1) - - elif args.multi: - for rt in args.multi.split(' '): - if not is_valid_rt(rt): - exit(1) - - else: - parser.print_help() - exit(1) - - exit(0) -- cgit v1.2.3 From f89d51293fb276fe64367b3019004c3e53a79821 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 14:51:19 +0100 Subject: bgp: T4042: bugfix route-distinguisher value range --- .../include/bgp/route-distinguisher.xml.i | 2 +- src/validators/bgp-rd-rt | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/interface-definitions/include/bgp/route-distinguisher.xml.i b/interface-definitions/include/bgp/route-distinguisher.xml.i index 6d0aa3ef1..8bc5b452e 100644 --- a/interface-definitions/include/bgp/route-distinguisher.xml.i +++ b/interface-definitions/include/bgp/route-distinguisher.xml.i @@ -7,7 +7,7 @@ Route Distinguisher, (x.x.x.x:yyy|xxxx:yyyy) - ^((25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)(\.(25[0-5]|2[0-4][0-9]|[1][0-9][0-9]|[1-9][0-9]|[0-9]?)){3}|[0-9]{1,10}):[0-9]{1,5}$ + diff --git a/src/validators/bgp-rd-rt b/src/validators/bgp-rd-rt index 24b0043f9..b2b69c9be 100755 --- a/src/validators/bgp-rd-rt +++ b/src/validators/bgp-rd-rt @@ -19,29 +19,37 @@ from vyos.template import is_ipv4 parser = ArgumentParser() group = parser.add_mutually_exclusive_group() +group.add_argument('--route-distinguisher', action='store', help='Validate BGP route distinguisher') group.add_argument('--route-target', action='store', help='Validate one BGP route-target') group.add_argument('--route-target-multi', action='store', help='Validate multiple, whitespace separated BGP route-targets') args = parser.parse_args() -def is_valid_rt(rt): - # every route target needs to have a colon and must consists of two parts +def is_valid(rt): + """ Verify BGP RD/RT - both can be verified using the same logic """ + # every RD/RT (route distinguisher/route target) needs to have a colon and + # must consists of two parts value = rt.split(':') if len(value) != 2: return False - # A route target must either be only numbers, or the first part must be an - # IPv4 address + + # An RD/RT must either be only numbers, or the first part must be an IPv4 + # address if (is_ipv4(value[0]) or value[0].isdigit()) and value[1].isdigit(): return True return False if __name__ == '__main__': - if args.route_target: - if not is_valid_rt(args.route_target): + if args.route_distinguisher: + if not is_valid(args.route_distinguisher): + exit(1) + + elif args.route_target: + if not is_valid(args.route_target): exit(1) elif args.route_target_multi: for rt in args.route_target_multi.split(' '): - if not is_valid_rt(rt): + if not is_valid(rt): exit(1) else: -- cgit v1.2.3 From 60b913c2f8a8eb21adee322a107de5a7ea8c1744 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 14:58:08 +0100 Subject: bgp: T4048: allow per VNI RD/RT configuration --- src/conf_mode/protocols_bgp.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index b88f0c4ef..03fb17ba7 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -255,14 +255,6 @@ def verify(bgp): tmp = dict_search(f'route_map.vpn.{export_import}', afi_config) if tmp: verify_route_map(tmp, bgp) - if afi in ['l2vpn_evpn'] and 'vrf' not in bgp: - # Some L2VPN EVPN AFI options are only supported under VRF - if 'vni' in afi_config: - for vni, vni_config in afi_config['vni'].items(): - if 'rd' in vni_config: - raise ConfigError('VNI route-distinguisher is only supported under EVPN VRF') - if 'route_target' in vni_config: - raise ConfigError('VNI route-target is only supported under EVPN VRF') return None -- cgit v1.2.3 From 2f31381416adda8f86d3b66d5e83fcf2b551df3f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 16:59:49 +0100 Subject: webproxy: T563: bugfix append-domain regex (cherry picked from commit e29e5c685809d8c18b5d3560c6cbcb4edce6eb82) --- interface-definitions/service_webproxy.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/service_webproxy.xml.in b/interface-definitions/service_webproxy.xml.in index d61a95690..03f504ac7 100644 --- a/interface-definitions/service_webproxy.xml.in +++ b/interface-definitions/service_webproxy.xml.in @@ -16,7 +16,7 @@ Domain to use for urls that do not contain a '.' - ^[\.][a-z0-9-][$]? + [.][A-Za-z0-9][-.A-Za-z0-9]* Must start append-domain with a '.' -- cgit v1.2.3 From 235aa6098fef3b398505835acbf42d4723b22d49 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 19:17:00 +0100 Subject: smoketest: vrrp: delete VLAN interfaces in tearDown() (cherry picked from commit 3fb6f5a966c57cb0936b35c13655f0b3f36483c4) --- smoketest/scripts/cli/test_ha_vrrp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py index 2524bf2b1..c14eea015 100755 --- a/smoketest/scripts/cli/test_ha_vrrp.py +++ b/smoketest/scripts/cli/test_ha_vrrp.py @@ -44,7 +44,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): for group in groups: vlan_id = group.lstrip('VLAN') - self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id]) + self.cli_delete(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id]) self.cli_delete(base_path) self.cli_commit() -- cgit v1.2.3 From f88112888d9b07f230f1294ce06a7714f957e694 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 19:17:30 +0100 Subject: smoketest: vrrp: passwords are only allowed 8 characters long (cherry picked from commit 55f6c1352fc5b2b8d051497e7fe97a4e62caf1fe) --- smoketest/scripts/cli/test_ha_vrrp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py index c14eea015..23a9f7796 100755 --- a/smoketest/scripts/cli/test_ha_vrrp.py +++ b/smoketest/scripts/cli/test_ha_vrrp.py @@ -108,7 +108,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): # Authentication self.cli_set(group_base + ['authentication', 'type', 'plaintext-password']) - self.cli_set(group_base + ['authentication', 'password', f'vyos-{group}']) + self.cli_set(group_base + ['authentication', 'password', f'{group}']) # commit changes self.cli_commit() @@ -129,7 +129,7 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' {vip}', config) # Authentication - self.assertIn(f'auth_pass "vyos-{group}"', config) + self.assertIn(f'auth_pass "{group}"', config) self.assertIn(f'auth_type PASS', config) def test_03_sync_group(self): -- cgit v1.2.3 From 319490e8c6534382ba21a6306cc4fc4b6d44db63 Mon Sep 17 00:00:00 2001 From: Eshenko Dmitriy Date: Sat, 4 Dec 2021 21:42:29 +0300 Subject: pppoe: T3403: Add missing space to the header --- src/op_mode/ppp-server-ctrl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/op_mode/ppp-server-ctrl.py b/src/op_mode/ppp-server-ctrl.py index 670cdf879..e93963fdd 100755 --- a/src/op_mode/ppp-server-ctrl.py +++ b/src/op_mode/ppp-server-ctrl.py @@ -60,7 +60,7 @@ def main(): output, err = popen(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][args.proto]) + args.action + ses_pattern, stderr=DEVNULL, decode='utf-8') if not err: try: - print(output) + print(f' {output}') except: sys.exit(0) else: -- cgit v1.2.3 From 025f0609cea8591e93b8cb4a7d0256e43e23323b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 5 Dec 2021 21:23:23 +0100 Subject: vrrp: T4041: bugfix sync-group transition-scripts not executed While mangling the config dict retrieved via get_config_dict() into a private representation of a configuration dictionary sync-groups were never accounted for. Instead everything always ended up in the regular vrrp transition-script section. The implementation has been changed to directly work on the content of get_config_dict() to stop any confusion and making redundant data copies obsolete. --- src/system/keepalived-fifo.py | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index 1fba0d75b..b1fe7e43f 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -29,6 +29,7 @@ from logging.handlers import SysLogHandler from vyos.ifconfig.vrrp import VRRP from vyos.configquery import ConfigTreeQuery from vyos.util import cmd +from vyos.util import dict_search # configure logging logger = logging.getLogger(__name__) @@ -69,22 +70,10 @@ class KeepalivedFifo: raise ValueError() # Read VRRP configuration directly from CLI - vrrp_config_dict = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True) - self.vrrp_config = {'vrrp_groups': {}, 'sync_groups': {}} - for key in ['group', 'sync_group']: - if key not in vrrp_config_dict: - continue - for group, group_config in vrrp_config_dict[key].items(): - if 'transition_script' not in group_config: - continue - self.vrrp_config['vrrp_groups'][group] = { - 'STOP': group_config['transition_script'].get('stop'), - 'FAULT': group_config['transition_script'].get('fault'), - 'BACKUP': group_config['transition_script'].get('backup'), - 'MASTER': group_config['transition_script'].get('master'), - } - logger.info(f'Loaded configuration: {self.vrrp_config}') + self.vrrp_config_dict = conf.get_config_dict(base, + key_mangling=('-', '_'), get_first_key=True) + + logger.debug(f'Loaded configuration: {self.vrrp_config_dict}') except Exception as err: logger.error(f'Unable to load configuration: {err}') @@ -129,20 +118,17 @@ class KeepalivedFifo: if os.path.exists(mdns_running_file): cmd(mdns_update_command) - if n_name in self.vrrp_config['vrrp_groups'] and n_state in self.vrrp_config['vrrp_groups'][n_name]: - n_script = self.vrrp_config['vrrp_groups'][n_name].get(n_state) - if n_script: - self._run_command(n_script) + tmp = dict_search(f'group.{n_name}.transition_script.{n_state.lower()}', self.vrrp_config_dict) + if tmp != None: + self._run_command(tmp) # check and run commands for VRRP sync groups - # currently, this is not available in VyOS CLI - if n_type == 'GROUP': + elif n_type == 'GROUP': if os.path.exists(mdns_running_file): cmd(mdns_update_command) - if n_name in self.vrrp_config['sync_groups'] and n_state in self.vrrp_config['sync_groups'][n_name]: - n_script = self.vrrp_config['sync_groups'][n_name].get(n_state) - if n_script: - self._run_command(n_script) + tmp = dict_search(f'sync_group.{n_name}.transition_script.{n_state.lower()}', self.vrrp_config_dict) + if tmp != None: + self._run_command(tmp) # mark task in queue as done self.message_queue.task_done() except Exception as err: -- cgit v1.2.3 From b6fbe6d3a5e8de4f90aa9fba61ca7491f9959ed0 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 6 Dec 2021 09:03:52 +0000 Subject: validators: T4052: Fix for warn message in the validator script Validator expects variable "script" for the Warning message But it gets undeclared "path" --- src/validators/script | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validators/script b/src/validators/script index 1d8a27e5c..46e8a57b2 100755 --- a/src/validators/script +++ b/src/validators/script @@ -37,6 +37,6 @@ if __name__ == '__main__': # File outside the config dir is just a warning if not vyos.util.file_is_persistent(script): sys.exit( - f'Warning: file {path} is outside the / config directory\n' + f'Warning: file {script} is outside the "/config" directory\n' 'It will not be automatically migrated to a new image on system update' ) -- cgit v1.2.3 From 001cc6655f1864a46b573dae13c8f33bbf224239 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 6 Dec 2021 10:13:32 +0000 Subject: validators: T4053: Fix exit code for script --- src/validators/script | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validators/script b/src/validators/script index 46e8a57b2..4ffdeb2a0 100755 --- a/src/validators/script +++ b/src/validators/script @@ -36,7 +36,7 @@ if __name__ == '__main__': # File outside the config dir is just a warning if not vyos.util.file_is_persistent(script): - sys.exit( + sys.exit(0)( f'Warning: file {script} is outside the "/config" directory\n' 'It will not be automatically migrated to a new image on system update' ) -- cgit v1.2.3 From bb77dd269bfb9522f5b56ac027598ac20e101f13 Mon Sep 17 00:00:00 2001 From: Viacheslav Date: Mon, 6 Dec 2021 15:09:36 +0000 Subject: sflow: T4046: Add source-address for sflow --- data/templates/netflow/uacctd.conf.tmpl | 3 +++ interface-definitions/flow-accounting-conf.xml.in | 1 + src/conf_mode/flow_accounting_conf.py | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/data/templates/netflow/uacctd.conf.tmpl b/data/templates/netflow/uacctd.conf.tmpl index 1c183bb20..11fc76769 100644 --- a/data/templates/netflow/uacctd.conf.tmpl +++ b/data/templates/netflow/uacctd.conf.tmpl @@ -68,5 +68,8 @@ sfprobe_agentip[sf_{{ server['address'] }}]: {{ templatecfg['sflow']['agent-addr {% if templatecfg['sflow']['sampling-rate'] != none %} sampling_rate[sf_{{ server['address'] }}]: {{ templatecfg['sflow']['sampling-rate'] }} {% endif %} +{% if templatecfg['sflow']['source-address'] != none %} +sfprobe_source_ip[sf_{{ server['address'] }}]: {{ templatecfg['sflow']['source-address'] }} +{% endif %} {% endfor %} {% endif %} diff --git a/interface-definitions/flow-accounting-conf.xml.in b/interface-definitions/flow-accounting-conf.xml.in index 113c1d849..b98794792 100644 --- a/interface-definitions/flow-accounting-conf.xml.in +++ b/interface-definitions/flow-accounting-conf.xml.in @@ -420,6 +420,7 @@ + #include diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py index 0a4559ade..daad00067 100755 --- a/src/conf_mode/flow_accounting_conf.py +++ b/src/conf_mode/flow_accounting_conf.py @@ -169,7 +169,8 @@ def get_config(): 'configured': vc.exists('system flow-accounting sflow'), 'agent-address': vc.return_value('system flow-accounting sflow agent-address'), 'sampling-rate': vc.return_value('system flow-accounting sflow sampling-rate'), - 'servers': None + 'servers': None, + 'source-address': vc.return_value('system flow-accounting sflow source-address') }, 'netflow': { 'configured': vc.exists('system flow-accounting netflow'), -- cgit v1.2.3 From 6f7c7bbabc1b65d6ae5181c180ea899277f33b58 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Dec 2021 18:41:46 +0100 Subject: mpls: ldp: T3753: adjust to new FRR 8.1 syntax --- data/templates/frr/ldpd.frr.tmpl | 3 +++ src/conf_mode/protocols_mpls.py | 38 +++++++++++++------------------------- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/data/templates/frr/ldpd.frr.tmpl b/data/templates/frr/ldpd.frr.tmpl index 0a5411552..bffe1b1c7 100644 --- a/data/templates/frr/ldpd.frr.tmpl +++ b/data/templates/frr/ldpd.frr.tmpl @@ -183,5 +183,8 @@ exit-address-family {% else %} no address-family ipv6 {% endif %} + ! {% endif %} +exit {% endif %} +! diff --git a/src/conf_mode/protocols_mpls.py b/src/conf_mode/protocols_mpls.py index 3b27608da..0b0c7d07b 100755 --- a/src/conf_mode/protocols_mpls.py +++ b/src/conf_mode/protocols_mpls.py @@ -66,36 +66,24 @@ def verify(mpls): def generate(mpls): # If there's no MPLS config generated, create dictionary key with no value. - if not mpls: - mpls['new_frr_config'] = '' + if not mpls or 'deleted' in mpls: return None - mpls['new_frr_config'] = render_to_string('frr/ldpd.frr.tmpl', mpls) + mpls['frr_ldpd_config'] = render_to_string('frr/ldpd.frr.tmpl', mpls) return None def apply(mpls): - # Define dictionary that will load FRR config - frr_cfg = {} + ldpd_damon = 'ldpd' + # Save original configuration prior to starting any commit actions - frr_cfg['original_config'] = frr.get_configuration(daemon='ldpd') - frr_cfg['modified_config'] = frr.replace_section(frr_cfg['original_config'], mpls['new_frr_config'], from_re='mpls.*') - - # If FRR config is blank, rerun the blank commit three times due to frr-reload - # behavior/bug not properly clearing out on one commit. - if mpls['new_frr_config'] == '': - for x in range(3): - frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd') - elif not 'ldp' in mpls: - for x in range(3): - frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd') - else: - # FRR mark configuration will test for syntax errors and throws an - # exception if any syntax errors is detected - frr.mark_configuration(frr_cfg['modified_config']) + frr_cfg = frr.FRRConfig() + + frr_cfg.load_configuration(ldpd_damon) + frr_cfg.modify_section(f'^mpls ldp', stop_pattern='^exit', remove_stop_mark=True) - # Commit resulting configuration to FRR, this will throw CommitError - # on failure - frr.reload_configuration(frr_cfg['modified_config'], daemon='ldpd') + if 'frr_ldpd_config' in mpls: + frr_cfg.add_before(frr.default_add_before, mpls['frr_ldpd_config']) + frr_cfg.commit_configuration(ldpd_damon) # Set number of entries in the platform label tables labels = '0' @@ -122,7 +110,7 @@ def apply(mpls): system_interfaces = [] # Populate system interfaces list with local MPLS capable interfaces for interface in glob('/proc/sys/net/mpls/conf/*'): - system_interfaces.append(os.path.basename(interface)) + system_interfaces.append(os.path.basename(interface)) # This is where the comparison is done on if an interface needs to be enabled/disabled. for system_interface in system_interfaces: interface_state = read_file(f'/proc/sys/net/mpls/conf/{system_interface}/input') @@ -138,7 +126,7 @@ def apply(mpls): system_interfaces = [] # If MPLS interfaces are not configured, set MPLS processing disabled for interface in glob('/proc/sys/net/mpls/conf/*'): - system_interfaces.append(os.path.basename(interface)) + system_interfaces.append(os.path.basename(interface)) for system_interface in system_interfaces: system_interface = system_interface.replace('.', '/') call(f'sysctl -wq net.mpls.conf.{system_interface}.input=0') -- cgit v1.2.3 From 3a29968d6b8dc0bc6966ae5a4713781ea8f9fff0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Dec 2021 18:42:02 +0100 Subject: mpls: ldp: T3753: add proper indention to Jinja2 template --- data/templates/frr/ldpd.frr.tmpl | 96 ++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/data/templates/frr/ldpd.frr.tmpl b/data/templates/frr/ldpd.frr.tmpl index bffe1b1c7..537ea4025 100644 --- a/data/templates/frr/ldpd.frr.tmpl +++ b/data/templates/frr/ldpd.frr.tmpl @@ -2,69 +2,69 @@ {% if ldp is defined %} mpls ldp {% if ldp.router_id is defined %} -router-id {{ ldp.router_id }} + router-id {{ ldp.router_id }} {% endif %} {% if ldp.parameters is defined %} {% if ldp.parameters.cisco_interop_tlv is defined %} -dual-stack cisco-interop + dual-stack cisco-interop {% endif %} {% if ldp.parameters.transport_prefer_ipv4 is defined%} -dual-stack transport-connection prefer ipv4 + dual-stack transport-connection prefer ipv4 {% endif %} {% if ldp.parameters.ordered_control is defined%} -ordered-control + ordered-control {% endif %} {% endif %} {% if ldp.neighbor is defined %} {% for neighbors in ldp.neighbor %} {% if ldp.neighbor[neighbors].password is defined %} -neighbor {{neighbors}} password {{ldp.neighbor[neighbors].password}} + neighbor {{ neighbors }} password {{ ldp.neighbor[neighbors].password }} {% endif %} {% if ldp.neighbor[neighbors].ttl_security is defined %} {% if 'disable' in ldp.neighbor[neighbors].ttl_security %} -neighbor {{neighbors}} ttl-security disable + neighbor {{ neighbors }} ttl-security disable {% else %} -neighbor {{neighbors}} ttl-security hops {{ldp.neighbor[neighbors].ttl_security}} + neighbor {{ neighbors }} ttl-security hops {{ ldp.neighbor[neighbors].ttl_security }} {% endif %} {% endif %} {% if ldp.neighbor[neighbors].session_holdtime is defined %} -neighbor {{neighbors}} session holdtime {{ldp.neighbor[neighbors].session_holdtime}} + neighbor {{ neighbors }} session holdtime {{ ldp.neighbor[neighbors].session_holdtime }} {% endif %} {% endfor %} {% endif %} -! + ! {% if ldp.discovery is defined %} {% if ldp.discovery.transport_ipv4_address is defined %} -address-family ipv4 + address-family ipv4 {% if ldp.allocation is defined %} {% if ldp.allocation.ipv4 is defined %} {% if ldp.allocation.ipv4.access_list is defined %} -label local allocate for {{ ldp.allocation.ipv4.access_list }} + label local allocate for {{ ldp.allocation.ipv4.access_list }} {% endif %} {% endif %} {% else %} -label local allocate host-routes + label local allocate host-routes {% endif %} {% if ldp.discovery.transport_ipv4_address is defined %} -discovery transport-address {{ ldp.discovery.transport_ipv4_address }} + discovery transport-address {{ ldp.discovery.transport_ipv4_address }} {% endif %} {% if ldp.discovery.hello_ipv4_holdtime is defined %} -discovery hello holdtime {{ ldp.discovery.hello_ipv4_holdtime }} + discovery hello holdtime {{ ldp.discovery.hello_ipv4_holdtime }} {% endif %} {% if ldp.discovery.hello_ipv4_interval is defined %} -discovery hello interval {{ ldp.discovery.hello_ipv4_interval }} + discovery hello interval {{ ldp.discovery.hello_ipv4_interval }} {% endif %} {% if ldp.discovery.session_ipv4_holdtime is defined %} -session holdtime {{ ldp.discovery.session_ipv4_holdtime }} + session holdtime {{ ldp.discovery.session_ipv4_holdtime }} {% endif %} {% if ldp.import is defined %} {% if ldp.import.ipv4 is defined %} {% if ldp.import.ipv4.import_filter is defined %} {% if ldp.import.ipv4.import_filter.filter_access_list is defined %} {% if ldp.import.ipv4.import_filter.neighbor_access_list is defined %} -label remote accept for {{ ldp.import.ipv4.import_filter.filter_access_list }} from {{ ldp.import.ipv4.import_filter.neighbor_access_list }} + label remote accept for {{ ldp.import.ipv4.import_filter.filter_access_list }} from {{ ldp.import.ipv4.import_filter.neighbor_access_list }} {% else %} -label remote accept for {{ ldp.import.ipv4.import_filter.filter_access_list }} + label remote accept for {{ ldp.import.ipv4.import_filter.filter_access_list }} {% endif %} {% endif %} {% endif %} @@ -73,14 +73,14 @@ label remote accept for {{ ldp.import.ipv4.import_filter.filter_access_list }} {% if ldp.export is defined %} {% if ldp.export.ipv4 is defined %} {% if ldp.export.ipv4.explicit_null is defined %} -label local advertise explicit-null + label local advertise explicit-null {% endif %} {% if ldp.export.ipv4.export_filter is defined %} {% if ldp.export.ipv4.export_filter.filter_access_list is defined %} {% if ldp.export.ipv4.export_filter.neighbor_access_list is defined %} -label local advertise for {{ ldp.export.ipv4.export_filter.filter_access_list }} to {{ ldp.export.ipv4.export_filter.neighbor_access_list }} + label local advertise for {{ ldp.export.ipv4.export_filter.filter_access_list }} to {{ ldp.export.ipv4.export_filter.neighbor_access_list }} {% else %} -label local advertise for {{ ldp.export.ipv4.export_filter.filter_access_list }} + label local advertise for {{ ldp.export.ipv4.export_filter.filter_access_list }} {% endif %} {% endif %} {% endif %} @@ -88,59 +88,59 @@ label local advertise for {{ ldp.export.ipv4.export_filter.filter_access_list }} {% endif %} {% if ldp.targeted_neighbor is defined %} {% if ldp.targeted_neighbor.ipv4.enable is defined %} -discovery targeted-hello accept + discovery targeted-hello accept {% endif %} {% if ldp.targeted_neighbor.ipv4.hello_holdtime is defined %} -discovery targeted-hello holdtime {{ ldp.targeted_neighbor.ipv4.hello_holdtime }} + discovery targeted-hello holdtime {{ ldp.targeted_neighbor.ipv4.hello_holdtime }} {% endif %} {% if ldp.targeted_neighbor.ipv4.hello_interval is defined %} -discovery targeted-hello interval {{ ldp.targeted_neighbor.ipv4.hello_interval }} + discovery targeted-hello interval {{ ldp.targeted_neighbor.ipv4.hello_interval }} {% endif %} {% for addresses in ldp.targeted_neighbor.ipv4.address %} -neighbor {{addresses}} targeted + neighbor {{addresses}} targeted {% endfor %} {% endif %} {% for interfaces in ldp.interface %} -interface {{interfaces}} + interface {{interfaces}} {% endfor %} -exit-address-family + exit-address-family {% else %} -no address-family ipv4 + no address-family ipv4 {% endif %} {% endif %} -! + ! {% if ldp.discovery is defined %} {% if ldp.discovery.transport_ipv6_address is defined %} -address-family ipv6 + address-family ipv6 {% if ldp.allocation is defined %} {% if ldp.allocation.ipv6 is defined %} {% if ldp.allocation.ipv6.access_list6 is defined %} -label local allocate for {{ ldp.allocation.ipv6.access_list6 }} + label local allocate for {{ ldp.allocation.ipv6.access_list6 }} {% endif %} {% endif %} {% else %} -label local allocate host-routes + label local allocate host-routes {% endif %} {% if ldp.discovery.transport_ipv6_address is defined %} -discovery transport-address {{ ldp.discovery.transport_ipv6_address }} + discovery transport-address {{ ldp.discovery.transport_ipv6_address }} {% endif %} {% if ldp.discovery.hello_ipv6_holdtime is defined %} -discovery hello holdtime {{ ldp.discovery.hello_ipv6_holdtime }} + discovery hello holdtime {{ ldp.discovery.hello_ipv6_holdtime }} {% endif %} {% if ldp.discovery.hello_ipv6_interval is defined %} -discovery hello interval {{ ldp.discovery.hello_ipv6_interval }} + discovery hello interval {{ ldp.discovery.hello_ipv6_interval }} {% endif %} {% if ldp.discovery.session_ipv6_holdtime is defined %} -session holdtime {{ ldp.discovery.session_ipv6_holdtime }} + session holdtime {{ ldp.discovery.session_ipv6_holdtime }} {% endif %} {% if ldp.import is defined %} {% if ldp.import.ipv6 is defined %} {% if ldp.import.ipv6.import_filter is defined %} {% if ldp.import.ipv6.import_filter.filter_access_list6 is defined %} {% if ldp.import.ipv6.import_filter.neighbor_access_list6 is defined %} -label remote accept for {{ ldp.import.ipv6.import_filter.filter_access_list6 }} from {{ ldp.import.ipv6.import_filter.neighbor_access_list6 }} + label remote accept for {{ ldp.import.ipv6.import_filter.filter_access_list6 }} from {{ ldp.import.ipv6.import_filter.neighbor_access_list6 }} {% else %} -label remote accept for {{ ldp.import.ipv6.import_filter.filter_access_list6 }} + label remote accept for {{ ldp.import.ipv6.import_filter.filter_access_list6 }} {% endif %} {% endif %} {% endif %} @@ -149,14 +149,14 @@ label remote accept for {{ ldp.import.ipv6.import_filter.filter_access_list6 }} {% if ldp.export is defined %} {% if ldp.export.ipv6 is defined %} {% if ldp.export.ipv6.explicit_null is defined %} -label local advertise explicit-null + label local advertise explicit-null {% endif %} {% if ldp.export.ipv6.export_filter is defined %} {% if ldp.export.ipv6.export_filter.filter_access_list6 is defined %} {% if ldp.export.ipv6.export_filter.neighbor_access_list6 is defined %} -label local advertise for {{ ldp.export.ipv6.export_filter.filter_access_list6 }} to {{ ldp.export.ipv6.export_filter.neighbor_access_list6 }} + label local advertise for {{ ldp.export.ipv6.export_filter.filter_access_list6 }} to {{ ldp.export.ipv6.export_filter.neighbor_access_list6 }} {% else %} -label local advertise for {{ ldp.export.ipv6.export_filter.filter_access_list6 }} + label local advertise for {{ ldp.export.ipv6.export_filter.filter_access_list6 }} {% endif %} {% endif %} {% endif %} @@ -164,24 +164,24 @@ label local advertise for {{ ldp.export.ipv6.export_filter.filter_access_list6 } {% endif %} {% if ldp.targeted_neighbor is defined %} {% if ldp.targeted_neighbor.ipv6.enable is defined %} -discovery targeted-hello accept + discovery targeted-hello accept {% endif %} {% if ldp.targeted_neighbor.ipv6.hello_holdtime is defined %} -discovery targeted-hello holdtime {{ ldp.targeted_neighbor.ipv6.hello_holdtime }} + discovery targeted-hello holdtime {{ ldp.targeted_neighbor.ipv6.hello_holdtime }} {% endif %} {% if ldp.targeted_neighbor.ipv6.hello_interval is defined %} -discovery targeted-hello interval {{ ldp.targeted_neighbor.ipv6.hello_interval }} + discovery targeted-hello interval {{ ldp.targeted_neighbor.ipv6.hello_interval }} {% endif %} {% for addresses in ldp.targeted_neighbor.ipv6.address %} -neighbor {{addresses}} targeted + neighbor {{addresses}} targeted {% endfor %} {% endif %} {% for interfaces in ldp.interface %} -interface {{interfaces}} + interface {{interfaces}} {% endfor %} -exit-address-family + exit-address-family {% else %} -no address-family ipv6 + no address-family ipv6 {% endif %} ! {% endif %} -- cgit v1.2.3 From 5b3ff8574c8265cf308b225c1a39503712f0b21d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Dec 2021 18:42:42 +0100 Subject: smoketests: mpls: add initial basic testcase --- smoketest/scripts/cli/test_protocols_mpls.py | 117 +++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100755 smoketest/scripts/cli/test_protocols_mpls.py diff --git a/smoketest/scripts/cli/test_protocols_mpls.py b/smoketest/scripts/cli/test_protocols_mpls.py new file mode 100755 index 000000000..13d38d01b --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_mpls.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.util import process_named_running + +PROCESS_NAME = 'ldpd' +base_path = ['protocols', 'mpls', 'ldp'] + +peers = { + '192.0.2.10' : { + 'intv_rx' : '500', + 'intv_tx' : '600', + 'multihop' : '', + 'source_addr': '192.0.2.254', + }, + '192.0.2.20' : { + 'echo_mode' : '', + 'intv_echo' : '100', + 'intv_mult' : '100', + 'intv_rx' : '222', + 'intv_tx' : '333', + 'passive' : '', + 'shutdown' : '', + }, + '2001:db8::a' : { + 'source_addr': '2001:db8::1', + }, + '2001:db8::b' : { + 'source_addr': '2001:db8::1', + 'multihop' : '', + }, +} + +profiles = { + 'foo' : { + 'echo_mode' : '', + 'intv_echo' : '100', + 'intv_mult' : '101', + 'intv_rx' : '222', + 'intv_tx' : '333', + 'shutdown' : '', + }, + 'bar' : { + 'intv_mult' : '102', + 'intv_rx' : '444', + 'passive' : '', + }, +} + +class TestProtocolsMPLS(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(cls, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_mpls_basic(self): + self.debug = True + router_id = '1.2.3.4' + transport_ipv4_addr = '5.6.7.8' + interfaces = Section.interfaces('ethernet') + + self.cli_set(base_path + ['router-id', router_id]) + + # At least one LDP interface must be configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + + # LDP transport address missing + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['discovery', 'transport-ipv4-address', transport_ipv4_addr]) + + # Commit changes + self.cli_commit() + + # Validate configuration + frrconfig = self.getFRRconfig('mpls ldp', daemon=PROCESS_NAME) + self.assertIn(f'mpls ldp', frrconfig) + self.assertIn(f' router-id {router_id}', frrconfig) + + # Validate AFI IPv4 + afiv4_config = self.getFRRconfig(' address-family ipv4', daemon=PROCESS_NAME) + self.assertIn(f' discovery transport-address {transport_ipv4_addr}', afiv4_config) + for interface in interfaces: + self.assertIn(f' interface {interface}', afiv4_config) + +if __name__ == '__main__': + unittest.main(verbosity=2, failfast=True) -- cgit v1.2.3 From ef2242e8e5c278c201ad825f4037668c86934443 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Dec 2021 19:59:33 +0100 Subject: bfd: T4054: bugfix missing profile assignment to peer --- data/templates/frr/bfdd.frr.tmpl | 3 +++ src/conf_mode/protocols_bfd.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/data/templates/frr/bfdd.frr.tmpl b/data/templates/frr/bfdd.frr.tmpl index e0e94c24d..439f79d67 100644 --- a/data/templates/frr/bfdd.frr.tmpl +++ b/data/templates/frr/bfdd.frr.tmpl @@ -41,6 +41,9 @@ bfd {% if peer_config.passive is defined %} passive-mode {% endif %} +{% if peer_config.profile is defined and peer_config.profile is not none %} + profile {{ peer_config.profile }} +{% endif %} {% if peer_config.shutdown is defined %} shutdown {% else %} diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 6981d0db1..caef61b3f 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -84,6 +84,11 @@ def verify(bfd): if 'source' in peer_config and 'interface' in peer_config['source']: raise ConfigError('Multihop and source interface cannot be used together') + if 'profile' in peer_config: + profile_name = peer_config['profile'] + if 'profile' not in bfd or profile_name not in bfd['profile']: + raise ConfigError(f'BFD profile "{profile_name}" does not exist!') + if 'vrf' in peer_config: verify_vrf(peer_config) -- cgit v1.2.3 From b6598b7f9f8c400b6c674a7220abb21f50737941 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Dec 2021 20:00:58 +0100 Subject: bfd: T1183: use unified error style --- src/conf_mode/protocols_bfd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index caef61b3f..8593da170 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -78,11 +78,11 @@ def verify(bfd): # multihop and echo-mode cannot be used together if 'echo_mode' in peer_config: - raise ConfigError('Multihop and echo-mode cannot be used together') + raise ConfigError('BFD multihop and echo-mode cannot be used together') # multihop doesn't accept interface names if 'source' in peer_config and 'interface' in peer_config['source']: - raise ConfigError('Multihop and source interface cannot be used together') + raise ConfigError('BFD multihop and source interface cannot be used together') if 'profile' in peer_config: profile_name = peer_config['profile'] -- cgit v1.2.3 From 7389a85ec91e9befaf16e3a8ed6d72199df0ab32 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Dec 2021 20:01:26 +0100 Subject: smoketest: bfd: T4054: verify peer -> profile assignment --- smoketest/scripts/cli/test_protocols_bfd.py | 33 +++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py index d33a64301..d234750b4 100755 --- a/smoketest/scripts/cli/test_protocols_bfd.py +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -40,6 +40,7 @@ peers = { 'intv_tx' : '333', 'passive' : '', 'shutdown' : '', + 'profile' : 'foo', 'source_intf': dum_if, }, '2001:db8::a' : { @@ -160,7 +161,16 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): if 'shutdown' in profile_config: self.cli_set(base_path + ['profile', profile, 'shutdown']) - self.cli_set(base_path + ['peer', peer, 'profile', list(profiles)[0]]) + for peer, peer_config in peers.items(): + if 'profile' in peer_config: + self.cli_set(base_path + ['peer', peer, 'profile', peer_config["profile"] + 'wrong']) + + # BFD profile does not exist! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for peer, peer_config in peers.items(): + if 'profile' in peer_config: + self.cli_set(base_path + ['peer', peer, 'profile', peer_config["profile"]]) # commit changes self.cli_commit() @@ -169,22 +179,27 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): for profile, profile_config in profiles.items(): config = self.getFRRconfig(f' profile {profile}', endsection='^ !') if 'echo_mode' in profile_config: - self.assertIn(f'echo-mode', config) + self.assertIn(f' echo-mode', config) if 'intv_echo' in profile_config: - self.assertIn(f'echo receive-interval {profile_config["intv_echo"]}', config) - self.assertIn(f'echo transmit-interval {profile_config["intv_echo"]}', config) + self.assertIn(f' echo receive-interval {profile_config["intv_echo"]}', config) + self.assertIn(f' echo transmit-interval {profile_config["intv_echo"]}', config) if 'intv_mult' in profile_config: - self.assertIn(f'detect-multiplier {profile_config["intv_mult"]}', config) + self.assertIn(f' detect-multiplier {profile_config["intv_mult"]}', config) if 'intv_rx' in profile_config: - self.assertIn(f'receive-interval {profile_config["intv_rx"]}', config) + self.assertIn(f' receive-interval {profile_config["intv_rx"]}', config) if 'intv_tx' in profile_config: - self.assertIn(f'transmit-interval {profile_config["intv_tx"]}', config) + self.assertIn(f' transmit-interval {profile_config["intv_tx"]}', config) if 'passive' in profile_config: - self.assertIn(f'passive-mode', config) + self.assertIn(f' passive-mode', config) if 'shutdown' in profile_config: - self.assertIn(f'shutdown', config) + self.assertIn(f' shutdown', config) else: self.assertNotIn(f'shutdown', config) + for peer, peer_config in peers.items(): + peerconfig = self.getFRRconfig(f' peer {peer}', end='') + if 'profile' in peer_config: + self.assertIn(f' profile {peer_config["profile"]}', peerconfig) + if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From 93b7c5f60ebe4d29ecde33db03b0eec8495ff104 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Dec 2021 20:06:03 +0100 Subject: https: pki: T3642: remove debug print() Remove superfluous print() statement added in commit 0852c588d55 ("https: pki: T3642: embed CA certificate into chain if specified"). --- src/conf_mode/https.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index 92dc4a410..86c6cd1b9 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -143,7 +143,6 @@ def generate(https): server_cert = str(wrap_certificate(pki_cert['certificate'])) if 'ca-certificate' in cert_dict: ca_cert = cert_dict['ca-certificate'] - print(ca_cert) server_cert += '\n' + str(wrap_certificate(https['pki']['ca'][ca_cert]['certificate'])) write_file(cert_path, server_cert) -- cgit v1.2.3 From 955f260ce682d64d27b3b11e618b1ae0176e4b91 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Dec 2021 20:57:20 +0100 Subject: https: T4055: add vrf support --- data/templates/https/override.conf.tmpl | 15 +++++++++++++++ interface-definitions/https.xml.in | 1 + src/conf_mode/https.py | 8 +++++++- 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 data/templates/https/override.conf.tmpl diff --git a/data/templates/https/override.conf.tmpl b/data/templates/https/override.conf.tmpl new file mode 100644 index 000000000..824b1ba3b --- /dev/null +++ b/data/templates/https/override.conf.tmpl @@ -0,0 +1,15 @@ +{% set vrf_command = 'ip vrf exec ' + vrf + ' ' if vrf is defined else '' %} +[Unit] +StartLimitIntervalSec=0 +After=vyos-router.service + +[Service] +ExecStartPre= +ExecStartPre={{vrf_command}}/usr/sbin/nginx -t -q -g 'daemon on; master_process on;' +ExecStart= +ExecStart={{vrf_command}}/usr/sbin/nginx -g 'daemon on; master_process on;' +ExecReload= +ExecReload={{vrf_command}}/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload +Restart=always +RestartPreventExitStatus= +RestartSec=10 diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in index f60df7c34..d26cd5e7a 100644 --- a/interface-definitions/https.xml.in +++ b/interface-definitions/https.xml.in @@ -143,6 +143,7 @@ + #include diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index 86c6cd1b9..cd5073aa2 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -23,6 +23,7 @@ import vyos.defaults import vyos.certbot_util from vyos.config import Config +from vyos.configverify import verify_vrf from vyos import ConfigError from vyos.pki import wrap_certificate from vyos.pki import wrap_private_key @@ -34,6 +35,7 @@ from vyos import airbag airbag.enable() config_file = '/etc/nginx/sites-available/default' +systemd_override = r'/etc/systemd/system/nginx.service.d/override.conf' cert_dir = '/etc/ssl/certs' key_dir = '/etc/ssl/private' certbot_dir = vyos.defaults.directories['certbot'] @@ -103,6 +105,8 @@ def verify(https): if not domains_found: raise ConfigError("At least one 'virtual-host server-name' " "matching the 'certbot domain-name' is required.") + + verify_vrf(https) return None def generate(https): @@ -208,10 +212,12 @@ def generate(https): } render(config_file, 'https/nginx.default.tmpl', data) - + render(systemd_override, 'https/override.conf.tmpl', https) return None def apply(https): + # Reload systemd manager configuration + call('systemctl daemon-reload') if https is not None: call('systemctl restart nginx.service') else: -- cgit v1.2.3 From 30c78e83ecd57e708c88d02e114702876f9ca312 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Dec 2021 20:57:47 +0100 Subject: xml: vrf: use "txt" in valueHelp --- interface-definitions/include/interface/vrf.xml.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/include/interface/vrf.xml.i b/interface-definitions/include/interface/vrf.xml.i index 5ad978a27..8605f56e8 100644 --- a/interface-definitions/include/interface/vrf.xml.i +++ b/interface-definitions/include/interface/vrf.xml.i @@ -3,7 +3,7 @@ VRF instance name - text + txt VRF instance name -- cgit v1.2.3 From d731caccf3eef486d483de032d423e979cd762f5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Dec 2021 20:57:56 +0100 Subject: xml: ntp: remove indent --- .../include/dhcp/ntp-server.xml.i | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/interface-definitions/include/dhcp/ntp-server.xml.i b/interface-definitions/include/dhcp/ntp-server.xml.i index 32d8207e5..4d7235aa1 100644 --- a/interface-definitions/include/dhcp/ntp-server.xml.i +++ b/interface-definitions/include/dhcp/ntp-server.xml.i @@ -1,15 +1,15 @@ - - - IP address of NTP server - - ipv4 - NTP server IPv4 address - - - - - - - + + + IP address of NTP server + + ipv4 + NTP server IPv4 address + + + + + + + -- cgit v1.2.3 From 49047b88c9bac0b2e007ccce7ac7d42e82ee0a2b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Dec 2021 21:09:42 +0100 Subject: bfd: T3753: FRR 8.1 uses a default echo-interval of 50 - reflect this in CLI --- interface-definitions/include/bfd-common.xml.i | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface-definitions/include/bfd-common.xml.i b/interface-definitions/include/bfd-common.xml.i index 8379784f7..f4dc659a5 100644 --- a/interface-definitions/include/bfd-common.xml.i +++ b/interface-definitions/include/bfd-common.xml.i @@ -54,12 +54,13 @@ Echo receive transmission interval u32:10-60000 - The minimal echo receive transmission interval that this system is capable of handling + The minimal echo receive transmission interval that this system is capable of handling (default: 50) + 50 -- cgit v1.2.3 From 8359d95d42a24d0b6bc61b3fe345ea144216d802 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 6 Dec 2021 21:16:31 +0100 Subject: Revert "bfd: T3753: FRR 8.1 uses a default echo-interval of 50 - reflect this in CLI" This reverts commit 49047b88c9bac0b2e007ccce7ac7d42e82ee0a2b. > Echo mode is only available for single hop sessions --- interface-definitions/include/bfd-common.xml.i | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface-definitions/include/bfd-common.xml.i b/interface-definitions/include/bfd-common.xml.i index f4dc659a5..8379784f7 100644 --- a/interface-definitions/include/bfd-common.xml.i +++ b/interface-definitions/include/bfd-common.xml.i @@ -54,13 +54,12 @@ Echo receive transmission interval u32:10-60000 - The minimal echo receive transmission interval that this system is capable of handling (default: 50) + The minimal echo receive transmission interval that this system is capable of handling - 50 -- cgit v1.2.3 From 37501f8baa9083dee8b7583a1036b58e77ccf735 Mon Sep 17 00:00:00 2001 From: goodNETnick Date: Tue, 7 Dec 2021 11:41:53 +1000 Subject: VRRP: T4033: VRRP script_security parameter removed --- data/templates/vrrp/keepalived.conf.tmpl | 3 --- 1 file changed, 3 deletions(-) diff --git a/data/templates/vrrp/keepalived.conf.tmpl b/data/templates/vrrp/keepalived.conf.tmpl index b4824a994..b93aa4bc9 100644 --- a/data/templates/vrrp/keepalived.conf.tmpl +++ b/data/templates/vrrp/keepalived.conf.tmpl @@ -5,9 +5,6 @@ global_defs { dynamic_interfaces script_user root - # Don't run scripts configured to be run as root if any part of the path - # is writable by a non-root user. - enable_script_security notify_fifo /run/keepalived/keepalived_notify_fifo notify_fifo_script /usr/libexec/vyos/system/keepalived-fifo.py } -- cgit v1.2.3 From 45d2429aa5d2ffafacdc5d9d00b7097169592427 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 7 Dec 2021 19:18:28 +0700 Subject: T3006: add a range validator --- src/validators/range | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100755 src/validators/range diff --git a/src/validators/range b/src/validators/range new file mode 100755 index 000000000..d4c25f3c4 --- /dev/null +++ b/src/validators/range @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 re +import sys +import argparse + +class MalformedRange(Exception): + pass + +def validate_range(value, min=None, max=None): + try: + lower, upper = re.match(r'^(\d+)-(\d+)$', value).groups() + + lower, upper = int(lower), int(upper) + + if int(lower) > int(upper): + raise MalformedRange("the lower bound exceeds the upper bound".format(value)) + + if min is not None: + if lower < min: + raise MalformedRange("the lower bound must not be less than {}".format(min)) + + if max is not None: + if upper > max: + raise MalformedRange("the upper bound must not be greater than {}".format(max)) + + except (AttributeError, ValueError): + raise MalformedRange("range syntax error") + +parser = argparse.ArgumentParser(description='Range validator.') +parser.add_argument('--min', type=int, action='store') +parser.add_argument('--max', type=int, action='store') +parser.add_argument('value', action='store') + +if __name__ == '__main__': + args = parser.parse_args() + + try: + validate_range(args.value, min=args.min, max=args.max) + except MalformedRange as e: + print("Incorrect range '{}': {}".format(args.value, e)) + sys.exit(1) -- cgit v1.2.3 From 3b6504d4bfc8a7f19613ee32fef5242711ca390a Mon Sep 17 00:00:00 2001 From: DmitriyEshenko Date: Mon, 6 Dec 2021 19:41:55 +0000 Subject: pppoe-server: T3006: Add range to regex generator --- data/templates/accel-ppp/pppoe.config.tmpl | 18 ++- interface-definitions/service_pppoe-server.xml.in | 18 ++- python/vyos/range_regex.py | 142 ++++++++++++++++++++++ src/conf_mode/service_pppoe-server.py | 15 +++ 4 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 python/vyos/range_regex.py diff --git a/data/templates/accel-ppp/pppoe.config.tmpl b/data/templates/accel-ppp/pppoe.config.tmpl index 238e7ee15..0a8e0079b 100644 --- a/data/templates/accel-ppp/pppoe.config.tmpl +++ b/data/templates/accel-ppp/pppoe.config.tmpl @@ -108,19 +108,17 @@ ac-name={{ access_concentrator }} {% if iface_config.vlan_id is not defined and iface_config.vlan_range is not defined %} interface={{ iface }} {% endif %} -{% if iface_config.vlan_id is defined and iface_config.vlan_range is not defined %} -{% for vlan in iface_config.vlan_id %} -interface={{ iface }}.{{ vlan }} -vlan-mon={{ iface }},{{ vlan }} +{% if iface_config.vlan_range is defined %} +{% for regex in iface_config.regex %} +interface=re:^{{ iface | replace('.', '\\.') }}\.({{ regex }})$ {% endfor %} -{% endif %} -{% if iface_config.vlan_range is defined and iface_config.vlan_id is not defined %} vlan-mon={{ iface }},{{ iface_config.vlan_range | join(',') }} -interface=re:{{ iface | replace('.', '\\.') }}\.\d+ {% endif %} -{% if iface_config.vlan_id is defined and iface_config.vlan_range is defined %} -vlan-mon={{ iface }},{{ iface_config.vlan_id | join(',') }},{{ iface_config.vlan_range | join(',') }} -interface=re:{{ iface | replace('.', '\\.') }}\.\d+ +{% if iface_config.vlan_id is defined %} +{% for vlan in iface_config.vlan_id %} +vlan-mon={{ iface }},{{ vlan }} +interface=re:^{{ iface | replace('.', '\\.') }}\.{{ vlan }}$ +{% endfor %} {% endif %} {% endfor %} {% endif %} diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in index 188aed6c4..97952d882 100644 --- a/interface-definitions/service_pppoe-server.xml.in +++ b/interface-definitions/service_pppoe-server.xml.in @@ -70,19 +70,27 @@ - VLAN monitor for the automatic creation of vlans (user per vlan) + VLAN monitor for the automatic creation of single vlan + + u32:1-4094 + VLAN monitor for the automatic creation of single vlan + - + - VLAN ID needs to be between 1 and 4096 + VLAN ID needs to be between 1 and 4094 - VLAN monitor for the automatic creation of vlans (user per vlan) + VLAN monitor for the automatic creation of vlans range + + start-end + VLAN monitor range for the automatic creation of vlans (e.g. 1-4094) + - (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/python/vyos/range_regex.py b/python/vyos/range_regex.py new file mode 100644 index 000000000..a8190d140 --- /dev/null +++ b/python/vyos/range_regex.py @@ -0,0 +1,142 @@ +'''Copyright (c) 2013, Dmitry Voronin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' +import math + +# coding=utf8 + +# Split range to ranges that has its unique pattern. +# Example for 12-345: +# +# 12- 19: 1[2-9] +# 20- 99: [2-9]\d +# 100-299: [1-2]\d{2} +# 300-339: 3[0-3]\d +# 340-345: 34[0-5] + +def range_to_regex(inpt_range): + if isinstance(inpt_range, str): + range_list = inpt_range.split('-') + # Check input arguments + if len(range_list) == 2: + # The first element in range must be higher then the second + if int(range_list[0]) < int(range_list[1]): + return regex_for_range(int(range_list[0]), int(range_list[1])) + + return None + +def bounded_regex_for_range(min_, max_): + return r'\b({})\b'.format(regex_for_range(min_, max_)) + +def regex_for_range(min_, max_): + """ + > regex_for_range(12, 345) + '1[2-9]|[2-9]\d|[1-2]\d{2}|3[0-3]\d|34[0-5]' + """ + positive_subpatterns = [] + negative_subpatterns = [] + + if min_ < 0: + min__ = 1 + if max_ < 0: + min__ = abs(max_) + max__ = abs(min_) + + negative_subpatterns = split_to_patterns(min__, max__) + min_ = 0 + + if max_ >= 0: + positive_subpatterns = split_to_patterns(min_, max_) + + negative_only_subpatterns = ['-' + val for val in negative_subpatterns if val not in positive_subpatterns] + positive_only_subpatterns = [val for val in positive_subpatterns if val not in negative_subpatterns] + intersected_subpatterns = ['-?' + val for val in negative_subpatterns if val in positive_subpatterns] + + subpatterns = negative_only_subpatterns + intersected_subpatterns + positive_only_subpatterns + return '|'.join(subpatterns) + + +def split_to_patterns(min_, max_): + subpatterns = [] + + start = min_ + for stop in split_to_ranges(min_, max_): + subpatterns.append(range_to_pattern(start, stop)) + start = stop + 1 + + return subpatterns + + +def split_to_ranges(min_, max_): + stops = {max_} + + nines_count = 1 + stop = fill_by_nines(min_, nines_count) + while min_ <= stop < max_: + stops.add(stop) + + nines_count += 1 + stop = fill_by_nines(min_, nines_count) + + zeros_count = 1 + stop = fill_by_zeros(max_ + 1, zeros_count) - 1 + while min_ < stop <= max_: + stops.add(stop) + + zeros_count += 1 + stop = fill_by_zeros(max_ + 1, zeros_count) - 1 + + stops = list(stops) + stops.sort() + + return stops + + +def fill_by_nines(integer, nines_count): + return int(str(integer)[:-nines_count] + '9' * nines_count) + + +def fill_by_zeros(integer, zeros_count): + return integer - integer % 10 ** zeros_count + + +def range_to_pattern(start, stop): + pattern = '' + any_digit_count = 0 + + for start_digit, stop_digit in zip(str(start), str(stop)): + if start_digit == stop_digit: + pattern += start_digit + elif start_digit != '0' or stop_digit != '9': + pattern += '[{}-{}]'.format(start_digit, stop_digit) + else: + any_digit_count += 1 + + if any_digit_count: + pattern += r'\d' + + if any_digit_count > 1: + pattern += '{{{}}}'.format(any_digit_count) + + return pattern \ No newline at end of file diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py index 9fbd531da..1f31d132d 100755 --- a/src/conf_mode/service_pppoe-server.py +++ b/src/conf_mode/service_pppoe-server.py @@ -24,8 +24,11 @@ from vyos.configverify import verify_accel_ppp_base_service from vyos.template import render from vyos.util import call from vyos.util import dict_search +from vyos.util import get_interface_config from vyos import ConfigError from vyos import airbag +from vyos.range_regex import range_to_regex + airbag.enable() pppoe_conf = r'/run/accel-pppd/pppoe.conf' @@ -56,6 +59,11 @@ def verify(pppoe): if 'interface' not in pppoe: raise ConfigError('At least one listen interface must be defined!') + # Check is interface exists in the system + for iface in pppoe['interface']: + if not get_interface_config(iface): + raise ConfigError(f'Interface {iface} does not exist!') + # local ippool and gateway settings config checks if not (dict_search('client_ip_pool.subnet', pppoe) or (dict_search('client_ip_pool.start', pppoe) and @@ -73,6 +81,13 @@ def generate(pppoe): if not pppoe: return None + # Generate special regex for dynamic interfaces + for iface in pppoe['interface']: + if 'vlan_range' in pppoe['interface'][iface]: + pppoe['interface'][iface]['regex'] = [] + for vlan_range in pppoe['interface'][iface]['vlan_range']: + pppoe['interface'][iface]['regex'].append(range_to_regex(vlan_range)) + render(pppoe_conf, 'accel-ppp/pppoe.config.tmpl', pppoe) if dict_search('authentication.mode', pppoe) == 'local': -- cgit v1.2.3 From bcfe967f607a83192d75c01e7f414655891eec60 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 8 Dec 2021 10:03:43 +0100 Subject: vrrp: T4059: do "late" read of the CLI configuration as this fails in __init__ ... thus we simply read the configuration the first time it really becomes necessary and a message requireing the data needs it actually. --- src/system/keepalived-fifo.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index b1fe7e43f..cde0c6cc5 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -53,7 +53,7 @@ class KeepalivedFifo: # parse arguments cmd_args = cmd_args_parser.parse_args() - self._config_load() + self.vrrp_config_dict = None self.pipe_path = cmd_args.PIPE # create queue for messages and events for syncronization @@ -97,6 +97,11 @@ class KeepalivedFifo: logger.debug('Message processing start') regex_notify = re.compile(r'^(?P\w+) "(?P[\w-]+)" (?P\w+) (?P\d+)$', re.MULTILINE) while self.stopme.is_set() is False: + # XXX: T4059 - do a "late" read of the CLI configuration as this would fail in __init__. + # Thus we simply read the configuration the first time it really becomes necessary + # and a message requireing the data needs it actually. + if self.vrrp_config_dict == None: + self._config_load() # wait for a new message event from pipe_wait self.message_event.wait() try: -- cgit v1.2.3 From e6b1c1f572068535ea99ee39a2d66bd6bf7043d5 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 8 Dec 2021 13:29:46 -0600 Subject: vyos.util: T4061: add function to check for completion of boot config --- python/vyos/configsource.py | 3 ++- python/vyos/defaults.py | 2 ++ python/vyos/util.py | 9 +++++++++ src/helpers/vyos-boot-config-loader.py | 4 ++-- src/helpers/vyos_net_name | 10 +--------- src/services/vyos-configd | 3 ++- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/python/vyos/configsource.py b/python/vyos/configsource.py index b0981d25e..a0f6a46b5 100644 --- a/python/vyos/configsource.py +++ b/python/vyos/configsource.py @@ -19,6 +19,7 @@ import re import subprocess from vyos.configtree import ConfigTree +from vyos.util import boot_configuration_complete class VyOSError(Exception): """ @@ -117,7 +118,7 @@ class ConfigSourceSession(ConfigSource): # Running config can be obtained either from op or conf mode, it always succeeds # once the config system is initialized during boot; # before initialization, set to empty string - if os.path.isfile('/tmp/vyos-config-status'): + if boot_configuration_complete(): try: running_config_text = self._run([self._cli_shell_api, '--show-active-only', '--show-show-defaults', '--show-ignore-edit', 'showConfig']) except VyOSError: diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index 00b14a985..f355c4919 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -29,6 +29,8 @@ directories = { "vyos_udev_dir": "/run/udev/vyos" } +config_status = '/tmp/vyos-config-status' + cfg_group = 'vyattacfg' cfg_vintage = 'vyos' diff --git a/python/vyos/util.py b/python/vyos/util.py index 157b26bf7..954c6670d 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -979,3 +979,12 @@ def is_wwan_connected(interface): # return True/False if interface is in connected state return dict_search('modem.generic.state', tmp) == 'connected' + +def boot_configuration_complete() -> bool: + """ Check if the boot config loader has completed + """ + from vyos.defaults import config_status + + if os.path.isfile(config_status): + return True + return False diff --git a/src/helpers/vyos-boot-config-loader.py b/src/helpers/vyos-boot-config-loader.py index c5bf22f10..b9cc87bfa 100755 --- a/src/helpers/vyos-boot-config-loader.py +++ b/src/helpers/vyos-boot-config-loader.py @@ -23,12 +23,12 @@ import grp import traceback from datetime import datetime -from vyos.defaults import directories +from vyos.defaults import directories, config_status from vyos.configsession import ConfigSession, ConfigSessionError from vyos.configtree import ConfigTree from vyos.util import cmd -STATUS_FILE = '/tmp/vyos-config-status' +STATUS_FILE = config_status TRACE_FILE = '/tmp/boot-config-trace' CFG_GROUP = 'vyattacfg' diff --git a/src/helpers/vyos_net_name b/src/helpers/vyos_net_name index e21d8c9ff..afeef8f2d 100755 --- a/src/helpers/vyos_net_name +++ b/src/helpers/vyos_net_name @@ -25,14 +25,13 @@ from sys import argv from vyos.configtree import ConfigTree from vyos.defaults import directories -from vyos.util import cmd +from vyos.util import cmd, boot_configuration_complete vyos_udev_dir = directories['vyos_udev_dir'] vyos_log_dir = '/run/udev/log' vyos_log_file = os.path.join(vyos_log_dir, 'vyos-net-name') config_path = '/opt/vyatta/etc/config/config.boot' -config_status = '/tmp/vyos-config-status' lock = threading.Lock() @@ -43,13 +42,6 @@ except FileExistsError: logging.basicConfig(filename=vyos_log_file, level=logging.DEBUG) -def boot_configuration_complete() -> bool: - """ Check if vyos-router has completed, hence hotplug event - """ - if os.path.isfile(config_status): - return True - return False - def is_available(intfs: dict, intf_name: str) -> bool: """ Check if interface name is already assigned """ diff --git a/src/services/vyos-configd b/src/services/vyos-configd index 670b6e66a..2d18589ef 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -28,6 +28,7 @@ import zmq from contextlib import contextmanager from vyos.defaults import directories +from vyos.util import boot_config_complete from vyos.configsource import ConfigSourceString, ConfigSourceError from vyos.config import Config from vyos import ConfigError @@ -186,7 +187,7 @@ def initialization(socket): session_out = None # if not a 'live' session, for example on boot, write to file - if not session_out or not os.path.isfile('/tmp/vyos-config-status'): + if not session_out or not boot_config_complete(): session_out = script_stdout_log session_mode = 'a' -- cgit v1.2.3 From e51b9444a6a6a4640445c22a7f71ba1e035876da Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 8 Dec 2021 11:56:59 -0600 Subject: configquery: T4060: allow use before boot configuration is complete --- python/vyos/configquery.py | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/python/vyos/configquery.py b/python/vyos/configquery.py index b981463e4..5b097b312 100644 --- a/python/vyos/configquery.py +++ b/python/vyos/configquery.py @@ -18,16 +18,15 @@ A small library that allows querying existence or value(s) of config settings from op mode, and execution of arbitrary op mode commands. ''' -import re -import json -from copy import deepcopy +import os from subprocess import STDOUT -import vyos.util -import vyos.xml +from vyos.util import popen, boot_configuration_complete from vyos.config import Config -from vyos.configtree import ConfigTree -from vyos.configsource import ConfigSourceSession +from vyos.configsource import ConfigSourceSession, ConfigSourceString +from vyos.defaults import directories + +config_file = os.path.join(directories['config'], 'config.boot') class ConfigQueryError(Exception): pass @@ -58,21 +57,21 @@ class CliShellApiConfigQuery(GenericConfigQuery): def exists(self, path: list): cmd = ' '.join(path) - (_, err) = vyos.util.popen(f'cli-shell-api existsActive {cmd}') + (_, err) = popen(f'cli-shell-api existsActive {cmd}') if err: return False return True def value(self, path: list): cmd = ' '.join(path) - (out, err) = vyos.util.popen(f'cli-shell-api returnActiveValue {cmd}') + (out, err) = popen(f'cli-shell-api returnActiveValue {cmd}') if err: raise ConfigQueryError('No value for given path') return out def values(self, path: list): cmd = ' '.join(path) - (out, err) = vyos.util.popen(f'cli-shell-api returnActiveValues {cmd}') + (out, err) = popen(f'cli-shell-api returnActiveValues {cmd}') if err: raise ConfigQueryError('No values for given path') return out @@ -81,25 +80,36 @@ class ConfigTreeQuery(GenericConfigQuery): def __init__(self): super().__init__() - config_source = ConfigSourceSession() - self.configtree = Config(config_source=config_source) + if boot_configuration_complete(): + config_source = ConfigSourceSession() + self.config = Config(config_source=config_source) + else: + try: + with open(config_file) as f: + config_string = f.read() + except OSError as err: + raise ConfigQueryError('No config file available') from err + + config_source = ConfigSourceString(running_config_text=config_string, + session_config_text=config_string) + self.config = Config(config_source=config_source) def exists(self, path: list): - return self.configtree.exists(path) + return self.config.exists(path) def value(self, path: list): - return self.configtree.return_value(path) + return self.config.return_value(path) def values(self, path: list): - return self.configtree.return_values(path) + return self.config.return_values(path) def list_nodes(self, path: list): - return self.configtree.list_nodes(path) + return self.config.list_nodes(path) def get_config_dict(self, path=[], effective=False, key_mangling=None, get_first_key=False, no_multi_convert=False, no_tag_node_value_mangle=False): - return self.configtree.get_config_dict(path, effective=effective, + return self.config.get_config_dict(path, effective=effective, key_mangling=key_mangling, get_first_key=get_first_key, no_multi_convert=no_multi_convert, no_tag_node_value_mangle=no_tag_node_value_mangle) @@ -110,7 +120,7 @@ class VbashOpRun(GenericOpRun): def run(self, path: list, **kwargs): cmd = ' '.join(path) - (out, err) = vyos.util.popen(f'/opt/vyatta/bin/vyatta-op-cmd-wrapper {cmd}', stderr=STDOUT, **kwargs) + (out, err) = popen(f'/opt/vyatta/bin/vyatta-op-cmd-wrapper {cmd}', stderr=STDOUT, **kwargs) if err: raise ConfigQueryError(out) return out -- cgit v1.2.3 From c643603bca8562e39991218daadebdb366237f48 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 9 Dec 2021 19:24:01 +0100 Subject: smoketest: vlan: T4064: verify IP addresses do not stick after deletion (cherry picked from commit 1a814661a0ade01f144398b91dd6998e42018fdd) --- smoketest/scripts/cli/base_interfaces_test.py | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 340ec4edd..e51ce5bc5 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -297,6 +297,23 @@ class BasicInterfaceTest: self.assertEqual(Interface(vif).get_admin_state(), 'up') + # T4064: Delete interface addresses, keep VLAN interface + for interface in self._interfaces: + base = self._base_path + [interface] + for vlan in self._vlan_range: + base = self._base_path + [interface, 'vif', vlan] + self.cli_delete(base + ['address']) + + self.cli_commit() + + # Verify no IP address is assigned + for interface in self._interfaces: + for vlan in self._vlan_range: + vif = f'{intf}.{vlan}' + for address in self._test_addr: + self.assertFalse(is_intf_addr_assigned(vif, address)) + + def test_vif_8021q_mtu_limits(self): # XXX: This testcase is not allowed to run as first testcase, reason # is the Wireless test will first load the wifi kernel hwsim module @@ -493,6 +510,24 @@ class BasicInterfaceTest: tmp = get_interface_config(vif) self.assertEqual(tmp['mtu'], int(self._mtu)) + + # T4064: Delete interface addresses, keep VLAN interface + for interface in self._interfaces: + base = self._base_path + [interface] + for vif_s in self._qinq_range: + for vif_c in self._vlan_range: + self.cli_delete(self._base_path + [interface, 'vif-s', vif_s, 'vif-c', vif_c, 'address']) + + self.cli_commit() + # Verify no IP address is assigned + for interface in self._interfaces: + base = self._base_path + [interface] + for vif_s in self._qinq_range: + for vif_c in self._vlan_range: + vif = f'{interface}.{vif_s}.{vif_c}' + for address in self._test_addr: + self.assertFalse(is_intf_addr_assigned(vif, address)) + # T3972: remove vif-c interfaces from vif-s for interface in self._interfaces: base = self._base_path + [interface] -- cgit v1.2.3 From a660276e888e6f598895304d4513a5c40dedc882 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 9 Dec 2021 21:48:50 +0100 Subject: vyos.configdict: T4064: bugfix for IP addresses not removed from Kernel Commit ee80d0aebd ("vyos.ifconfig: T2738: do not remove OS assigned IP addresses from interface") addressed an issue with IP addresses added to interfaces by daemons and not by the CLI. The solution in this commit for IP address removal unfortunately did not cover VLAN (802.1q and 802.1ad) IP address removal in the same way as it is done for non VLAN interfaces. The code was missing. (cherry picked from commit 91898b8bd876af6b4d7fae54981e78400f57e008) --- python/vyos/configdict.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 425a2e416..d974a7565 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -459,7 +459,10 @@ def get_interface_dict(config, base, ifname=''): # Only add defaults if interface is not about to be deleted - this is # to keep a cleaner config dict. if 'deleted' not in dict: - dict['vif'][vif] = dict_merge(default_vif_values, vif_config) + address = leaf_node_changed(config, ['vif', vif, 'address']) + if address: dict['vif'][vif].update({'address_old' : address}) + + dict['vif'][vif] = dict_merge(default_vif_values, dict['vif'][vif]) # XXX: T2665: blend in proper DHCPv6-PD default values dict['vif'][vif] = T2665_set_dhcpv6pd_defaults(dict['vif'][vif]) @@ -480,7 +483,11 @@ def get_interface_dict(config, base, ifname=''): # Only add defaults if interface is not about to be deleted - this is # to keep a cleaner config dict. if 'deleted' not in dict: - dict['vif_s'][vif_s] = dict_merge(default_vif_s_values, vif_s_config) + address = leaf_node_changed(config, ['vif-s', vif_s, 'address']) + if address: dict['vif_s'][vif_s].update({'address_old' : address}) + + dict['vif_s'][vif_s] = dict_merge(default_vif_s_values, + dict['vif_s'][vif_s]) # XXX: T2665: blend in proper DHCPv6-PD default values dict['vif_s'][vif_s] = T2665_set_dhcpv6pd_defaults(dict['vif_s'][vif_s]) @@ -499,8 +506,12 @@ def get_interface_dict(config, base, ifname=''): # Only add defaults if interface is not about to be deleted - this is # to keep a cleaner config dict. if 'deleted' not in dict: + address = leaf_node_changed(config, ['vif-s', vif_s, 'vif-c', vif_c, 'address']) + if address: dict['vif_s'][vif_s]['vif_c'][vif_c].update( + {'address_old' : address}) + dict['vif_s'][vif_s]['vif_c'][vif_c] = dict_merge( - default_vif_c_values, vif_c_config) + default_vif_c_values, dict['vif_s'][vif_s]['vif_c'][vif_c]) # XXX: T2665: blend in proper DHCPv6-PD default values dict['vif_s'][vif_s]['vif_c'][vif_c] = T2665_set_dhcpv6pd_defaults( dict['vif_s'][vif_s]['vif_c'][vif_c]) -- cgit v1.2.3 From afa50e9d39d7e2599719c411f523d9205c33ad3b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 9 Dec 2021 22:38:45 +0100 Subject: Revert "vrrp: T4059: do "late" read of the CLI configuration as this fails in __init__" This reverts commit bcfe967f607a83192d75c01e7f414655891eec60. --- src/system/keepalived-fifo.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py index cde0c6cc5..b1fe7e43f 100755 --- a/src/system/keepalived-fifo.py +++ b/src/system/keepalived-fifo.py @@ -53,7 +53,7 @@ class KeepalivedFifo: # parse arguments cmd_args = cmd_args_parser.parse_args() - self.vrrp_config_dict = None + self._config_load() self.pipe_path = cmd_args.PIPE # create queue for messages and events for syncronization @@ -97,11 +97,6 @@ class KeepalivedFifo: logger.debug('Message processing start') regex_notify = re.compile(r'^(?P\w+) "(?P[\w-]+)" (?P\w+) (?P\d+)$', re.MULTILINE) while self.stopme.is_set() is False: - # XXX: T4059 - do a "late" read of the CLI configuration as this would fail in __init__. - # Thus we simply read the configuration the first time it really becomes necessary - # and a message requireing the data needs it actually. - if self.vrrp_config_dict == None: - self._config_load() # wait for a new message event from pipe_wait self.message_event.wait() try: -- cgit v1.2.3 From 0ec938326ffd63ad5eab5f3be3038e13fe3118ec Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 9 Dec 2021 23:17:47 +0100 Subject: xml: include: create dedicated bfd subfolder --- interface-definitions/include/bfd-common.xml.i | 78 ---------------------- interface-definitions/include/bfd.xml.i | 8 --- interface-definitions/include/bfd/bfd.xml.i | 10 +++ interface-definitions/include/bfd/common.xml.i | 78 ++++++++++++++++++++++ .../include/isis/protocol-common-config.xml.i | 2 +- .../include/ospf/interface-common.xml.i | 2 +- interface-definitions/protocols-bfd.xml.in | 4 +- 7 files changed, 92 insertions(+), 90 deletions(-) delete mode 100644 interface-definitions/include/bfd-common.xml.i delete mode 100644 interface-definitions/include/bfd.xml.i create mode 100644 interface-definitions/include/bfd/bfd.xml.i create mode 100644 interface-definitions/include/bfd/common.xml.i diff --git a/interface-definitions/include/bfd-common.xml.i b/interface-definitions/include/bfd-common.xml.i deleted file mode 100644 index 8379784f7..000000000 --- a/interface-definitions/include/bfd-common.xml.i +++ /dev/null @@ -1,78 +0,0 @@ - - - - Enables the echo transmission mode - - - - - - Configure timer intervals - - - - - Minimum interval of receiving control packets - - u32:10-60000 - Interval in milliseconds (default: 300) - - - - - - 300 - - - - Minimum interval of transmitting control packets - - u32:10-60000 - Interval in milliseconds (default: 300) - - - - - - 300 - - - - Multiplier to determine packet loss - - u32:2-255 - Remote transmission interval will be multiplied by this value (default: 3) - - - - - - 3 - - - - Echo receive transmission interval - - u32:10-60000 - The minimal echo receive transmission interval that this system is capable of handling - - - - - - - - - - - Do not attempt to start sessions - - - - - - Disable this peer - - - - diff --git a/interface-definitions/include/bfd.xml.i b/interface-definitions/include/bfd.xml.i deleted file mode 100644 index 2bc3664e1..000000000 --- a/interface-definitions/include/bfd.xml.i +++ /dev/null @@ -1,8 +0,0 @@ - - - - Enable Bidirectional Forwarding Detection (BFD) - - - - diff --git a/interface-definitions/include/bfd/bfd.xml.i b/interface-definitions/include/bfd/bfd.xml.i new file mode 100644 index 000000000..022956d98 --- /dev/null +++ b/interface-definitions/include/bfd/bfd.xml.i @@ -0,0 +1,10 @@ + + + + Enable Bidirectional Forwarding Detection (BFD) + + + #include + + + diff --git a/interface-definitions/include/bfd/common.xml.i b/interface-definitions/include/bfd/common.xml.i new file mode 100644 index 000000000..e52221441 --- /dev/null +++ b/interface-definitions/include/bfd/common.xml.i @@ -0,0 +1,78 @@ + + + + Enables the echo transmission mode + + + + + + Configure timer intervals + + + + + Minimum interval of receiving control packets + + u32:10-60000 + Interval in milliseconds (default: 300) + + + + + + 300 + + + + Minimum interval of transmitting control packets + + u32:10-60000 + Interval in milliseconds (default: 300) + + + + + + 300 + + + + Multiplier to determine packet loss + + u32:2-255 + Remote transmission interval will be multiplied by this value (default: 3) + + + + + + 3 + + + + Echo receive transmission interval + + u32:10-60000 + The minimal echo receive transmission interval that this system is capable of handling + + + + + + + + + + + Do not attempt to start sessions + + + + + + Disable this peer + + + + diff --git a/interface-definitions/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i index 84e2f7bb2..8ffa14a19 100644 --- a/interface-definitions/include/isis/protocol-common-config.xml.i +++ b/interface-definitions/include/isis/protocol-common-config.xml.i @@ -648,7 +648,7 @@ - #include + #include Configure circuit type for interface diff --git a/interface-definitions/include/ospf/interface-common.xml.i b/interface-definitions/include/ospf/interface-common.xml.i index 4b0aef380..738651594 100644 --- a/interface-definitions/include/ospf/interface-common.xml.i +++ b/interface-definitions/include/ospf/interface-common.xml.i @@ -1,5 +1,5 @@ -#include +#include Interface cost diff --git a/interface-definitions/protocols-bfd.xml.in b/interface-definitions/protocols-bfd.xml.in index d5a968001..265c9603a 100644 --- a/interface-definitions/protocols-bfd.xml.in +++ b/interface-definitions/protocols-bfd.xml.in @@ -66,7 +66,7 @@ - #include + #include Allow this BFD peer to not be directly connected @@ -88,7 +88,7 @@ - #include + #include -- cgit v1.2.3 From e639b84e56a131e6391aa94adb1c1937f3c8dc60 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 9 Dec 2021 23:19:19 +0100 Subject: xml: T4058: provide building block for BFD profiles --- interface-definitions/include/bfd/profile.xml.i | 14 ++++++++++++++ interface-definitions/protocols-bfd.xml.in | 13 +------------ 2 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 interface-definitions/include/bfd/profile.xml.i diff --git a/interface-definitions/include/bfd/profile.xml.i b/interface-definitions/include/bfd/profile.xml.i new file mode 100644 index 000000000..5ff057286 --- /dev/null +++ b/interface-definitions/include/bfd/profile.xml.i @@ -0,0 +1,14 @@ + + + + Use settings from BFD profile + + protocols bfd profile + + + txt + BFD profile name + + + + diff --git a/interface-definitions/protocols-bfd.xml.in b/interface-definitions/protocols-bfd.xml.in index 265c9603a..a9957d884 100644 --- a/interface-definitions/protocols-bfd.xml.in +++ b/interface-definitions/protocols-bfd.xml.in @@ -26,18 +26,7 @@ - - - Use settings from BFD profile - - protocols bfd profile - - - txt - BFD profile name - - - + #include Bind listener to specified interface/address, mandatory for IPv6 -- cgit v1.2.3 From 006b17a319c540a2fb384b90c65294f5a607787c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 9 Dec 2021 23:19:43 +0100 Subject: bgp: T4058: add support for BFD profiles --- data/templates/frr/bgpd.frr.tmpl | 6 ++++++ interface-definitions/include/bgp/neighbor-bfd.xml.i | 1 + smoketest/scripts/cli/test_protocols_bgp.py | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index fbdbafd6e..a3c6431f6 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -17,6 +17,12 @@ {% endif %} {% if config.bfd is defined %} neighbor {{ neighbor }} bfd +{% if config.bfd.check_control_plane_failure is defined %} + neighbor {{ neighbor }} bfd check-control-plane-failure +{% endif %} +{% if config.bfd.profile is defined and config.bfd.profile is not none %} + neighbor {{ neighbor }} bfd profile {{ config.bfd.profile }} +{% endif %} {% endif %} {% if config.capability is defined and config.capability is not none %} {% if config.capability.dynamic is defined %} diff --git a/interface-definitions/include/bgp/neighbor-bfd.xml.i b/interface-definitions/include/bgp/neighbor-bfd.xml.i index d486bdd8a..fac2a1166 100644 --- a/interface-definitions/include/bgp/neighbor-bfd.xml.i +++ b/interface-definitions/include/bgp/neighbor-bfd.xml.i @@ -4,6 +4,7 @@ Enable Bidirectional Forwarding Detection (BFD) support + #include Allow to write CBIT independence in BFD outgoing packets and read both C-BIT value of BFD and lookup BGP peer status diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 16284ed01..5fdca4fc2 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -32,9 +32,11 @@ prefix_list_in = 'pfx-foo-in' prefix_list_out = 'pfx-foo-out' prefix_list_in6 = 'pfx-foo-in6' prefix_list_out6 = 'pfx-foo-out6' +bfd_profile = 'foo-bar-baz' neighbor_config = { '192.0.2.1' : { + 'bfd' : '', 'cap_dynamic' : '', 'cap_ext_next' : '', 'remote_as' : '100', @@ -51,6 +53,7 @@ neighbor_config = { 'addpath_all' : '', }, '192.0.2.2' : { + 'bfd_profile' : bfd_profile, 'remote_as' : '200', 'shutdown' : '', 'no_cap_nego' : '', @@ -98,6 +101,7 @@ neighbor_config = { peer_group_config = { 'foo' : { + 'bfd' : '', 'remote_as' : '100', 'passive' : '', 'password' : 'VyOS-Secure123', @@ -116,6 +120,7 @@ peer_group_config = { 'no_send_comm_ext' : '', }, 'baz' : { + 'bfd_profile' : bfd_profile, 'cap_dynamic' : '', 'cap_ext_next' : '', 'remote_as' : '200', @@ -154,6 +159,11 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): def verify_frr_config(self, peer, peer_config, frrconfig): # recurring patterns to verify for both a simple neighbor and a peer-group + if 'bfd' in peer_config: + self.assertIn(f' neighbor {peer} bfd', frrconfig) + if 'bfd_profile' in peer_config: + self.assertIn(f' neighbor {peer} bfd profile {peer_config["bfd_profile"]}', frrconfig) + self.assertIn(f' neighbor {peer} bfd check-control-plane-failure', frrconfig) if 'cap_dynamic' in peer_config: self.assertIn(f' neighbor {peer} capability dynamic', frrconfig) if 'cap_ext_next' in peer_config: @@ -270,6 +280,11 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'adv_interv' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'advertisement-interval', peer_config["adv_interv"]]) + if 'bfd' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'bfd']) + if 'bfd_profile' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'bfd', 'profile', peer_config["bfd_profile"]]) + self.cli_set(base_path + ['neighbor', peer, 'bfd', 'check-control-plane-failure']) if 'cap_dynamic' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'capability', 'dynamic']) if 'cap_ext_next' in peer_config: @@ -339,6 +354,11 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): def test_bgp_03_peer_groups(self): # Test out individual peer-group configuration items for peer_group, config in peer_group_config.items(): + if 'bfd' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'bfd']) + if 'bfd_profile' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'bfd', 'profile', config["bfd_profile"]]) + self.cli_set(base_path + ['peer-group', peer_group, 'bfd', 'check-control-plane-failure']) if 'cap_dynamic' in config: self.cli_set(base_path + ['peer-group', peer_group, 'capability', 'dynamic']) if 'cap_ext_next' in config: -- cgit v1.2.3 From 1e70157b16e0b69fffaf8c274846fffb286b43c3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 9 Dec 2021 23:19:52 +0100 Subject: isis: T4058: add support for BFD profiles --- data/templates/frr/isisd.frr.tmpl | 3 +++ smoketest/scripts/cli/test_protocols_isis.py | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/data/templates/frr/isisd.frr.tmpl b/data/templates/frr/isisd.frr.tmpl index fc0799e02..b1e3f825b 100644 --- a/data/templates/frr/isisd.frr.tmpl +++ b/data/templates/frr/isisd.frr.tmpl @@ -6,6 +6,9 @@ interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} ipv6 router isis VyOS {% if iface_config.bfd is defined %} isis bfd +{% if iface_config.bfd.profile is defined and iface_config.bfd.profile is not none %} + isis bfd profile {{ iface_config.bfd.profile }} +{% endif %} {% endif %} {% if iface_config.network is defined and iface_config.network.point_to_point is defined %} isis network point-to-point diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index e42040025..7f51c7178 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -198,17 +198,19 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.assertIn(f' area-password clear {password}', tmp) - def test_isis_06_spf_delay(self): + def test_isis_06_spf_delay_bfd(self): network = 'point-to-point' holddown = '10' init_delay = '50' long_delay = '200' short_delay = '100' time_to_learn = '75' + bfd_profile = 'isis-bfd' self.cli_set(base_path + ['net', net]) for interface in self._interfaces: self.cli_set(base_path + ['interface', interface, 'network', network]) + self.cli_set(base_path + ['interface', interface, 'bfd', 'profile', bfd_profile]) self.cli_set(base_path + ['spf-delay-ietf', 'holddown', holddown]) # verify() - All types of spf-delay must be configured @@ -244,6 +246,8 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.assertIn(f' ip router isis {domain}', tmp) self.assertIn(f' ipv6 router isis {domain}', tmp) self.assertIn(f' isis network {network}', tmp) + self.assertIn(f' isis bfd', tmp) + self.assertIn(f' isis bfd profile {bfd_profile}', tmp) if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From a99d31c730ae71467e6f82754fdd9412bcd633dc Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 9 Dec 2021 23:20:02 +0100 Subject: ospf(v3): T4058: add support for BFD profiles --- data/templates/frr/ospf6d.frr.tmpl | 3 +++ data/templates/frr/ospfd.frr.tmpl | 3 +++ smoketest/scripts/cli/test_protocols_ospf.py | 4 +++- smoketest/scripts/cli/test_protocols_ospfv3.py | 4 +++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/data/templates/frr/ospf6d.frr.tmpl b/data/templates/frr/ospf6d.frr.tmpl index 10a6d9b4b..c366326bf 100644 --- a/data/templates/frr/ospf6d.frr.tmpl +++ b/data/templates/frr/ospf6d.frr.tmpl @@ -25,6 +25,9 @@ interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% endif %} {% if iface_config.bfd is defined %} ipv6 ospf6 bfd +{% if iface_config.bfd.profile is defined and iface_config.bfd.profile is not none %} + ipv6 ospf6 bfd profile {{ iface_config.bfd.profile }} +{% endif %} {% endif %} {% if iface_config.mtu_ignore is defined %} ipv6 ospf6 mtu-ignore diff --git a/data/templates/frr/ospfd.frr.tmpl b/data/templates/frr/ospfd.frr.tmpl index a7b770f07..af66baf53 100644 --- a/data/templates/frr/ospfd.frr.tmpl +++ b/data/templates/frr/ospfd.frr.tmpl @@ -42,6 +42,9 @@ interface {{ iface }} {{ 'vrf ' + vrf if vrf is defined and vrf is not none }} {% endif %} {% if iface_config.bfd is defined %} ip ospf bfd +{% if iface_config.bfd.profile is defined and iface_config.bfd.profile is not none %} + ip ospf bfd profile {{ iface_config.bfd.profile }} +{% endif %} {% endif %} {% if iface_config.mtu_ignore is defined %} ip ospf mtu-ignore diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 04853c5fe..5783c5efb 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -251,13 +251,14 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): cost = '150' network = 'point-to-point' priority = '200' + bfd_profile = 'vyos-test' self.cli_set(base_path + ['passive-interface', 'default']) for interface in interfaces: base_interface = base_path + ['interface', interface] self.cli_set(base_interface + ['authentication', 'plaintext-password', password]) self.cli_set(base_interface + ['bandwidth', bandwidth]) - self.cli_set(base_interface + ['bfd']) + self.cli_set(base_interface + ['bfd', 'profile', bfd_profile]) self.cli_set(base_interface + ['cost', cost]) self.cli_set(base_interface + ['mtu-ignore']) self.cli_set(base_interface + ['network', network]) @@ -272,6 +273,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.assertIn(f'interface {interface}', config) self.assertIn(f' ip ospf authentication-key {password}', config) self.assertIn(f' ip ospf bfd', config) + self.assertIn(f' ip ospf bfd profile {bfd_profile}', config) self.assertIn(f' ip ospf cost {cost}', config) self.assertIn(f' ip ospf mtu-ignore', config) self.assertIn(f' ip ospf network {network}', config) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index f0557f640..40dd254a8 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -110,6 +110,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.assertIn(f' redistribute {protocol} route-map {route_map}', frrconfig) def test_ospfv3_04_interfaces(self): + bfd_profile = 'vyos-ipv6' self.cli_set(base_path + ['parameters', 'router-id', router_id]) self.cli_set(base_path + ['area', default_area]) @@ -119,7 +120,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): interfaces = Section.interfaces('ethernet') for interface in interfaces: if_base = base_path + ['interface', interface] - self.cli_set(if_base + ['bfd']) + self.cli_set(if_base + ['bfd', 'profile', bfd_profile]) self.cli_set(if_base + ['cost', cost]) self.cli_set(if_base + ['instance-id', '0']) self.cli_set(if_base + ['mtu-ignore']) @@ -142,6 +143,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): if_config = self.getFRRconfig(f'interface {interface}') self.assertIn(f'interface {interface}', if_config) self.assertIn(f' ipv6 ospf6 bfd', if_config) + self.assertIn(f' ipv6 ospf6 bfd profile {bfd_profile}', if_config) self.assertIn(f' ipv6 ospf6 cost {cost}', if_config) self.assertIn(f' ipv6 ospf6 mtu-ignore', if_config) self.assertIn(f' ipv6 ospf6 network point-to-point', if_config) -- cgit v1.2.3 From 482384102acd0af95ff966448a5253538b58c957 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 4 Dec 2021 16:59:49 +0100 Subject: T562: bugfix missing "," on variable listing This prevented VyOS to actually boot as the configuration could not be loaded, as "system host-name" was unable to commit. --- src/services/vyos-hostsd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index 1e0b37efe..df9f18d2d 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -289,7 +289,7 @@ base_schema = Schema({ Required('op'): Any('add', 'delete', 'set', 'get', 'apply'), 'type': Any('name_servers', 'name_server_tags_recursor', 'name_server_tags_system', - 'forward_zones', 'authoritative_zones' 'search_domains', + 'forward_zones', 'authoritative_zones', 'search_domains', 'hosts', 'host_name'), 'data': Any(list, dict), 'tag': str, -- cgit v1.2.3 From 7f665a92b3ad3978b17153726de23c80ba97c4b7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 10 Dec 2021 19:59:11 +0100 Subject: smoketest: interfaces: bugfix loop iteration - same config set multiple times --- smoketest/scripts/cli/base_interfaces_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index e51ce5bc5..bc0a6c128 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -171,10 +171,10 @@ class BasicInterfaceTest: def test_add_multiple_ip_addresses(self): # Add address for intf in self._interfaces: + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) for addr in self._test_addr: self.cli_set(self._base_path + [intf, 'address', addr]) - for option in self._options.get(intf, []): - self.cli_set(self._base_path + [intf] + option.split()) self.cli_commit() -- cgit v1.2.3 From 4948380a588dea59a01e6e33b9b8e1524ba6a4ed Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 10 Dec 2021 20:48:35 +0100 Subject: ConfigError: T4068: automatically wrap message at 72 characters --- python/vyos/base.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/vyos/base.py b/python/vyos/base.py index 4e23714e5..c78045548 100644 --- a/python/vyos/base.py +++ b/python/vyos/base.py @@ -1,4 +1,4 @@ -# Copyright 2018 VyOS maintainers and contributors +# Copyright 2018-2021 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -13,6 +13,11 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . +from textwrap import fill class ConfigError(Exception): - pass + def __init__(self, message): + # Reformat the message and trim it to 72 characters in length + message = fill(message, width=72) + # Call the base class constructor with the parameters it needs + super().__init__(message) -- cgit v1.2.3 From eb29d8d5a0bc536364b4024ec6c336451b58ba49 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 10 Dec 2021 20:54:44 +0100 Subject: vxlan: T3700: add support for external controlled FDB Background information [1]. Specifies whether an external control plane (e.g. ip route encap/EVPN) or the internal FDB should be used. [1]: https://legacy.netdevconf.info/2.2/slides/prabhu-linuxbridge-tutorial.pdf --- interface-definitions/interfaces-vxlan.xml.in | 6 ++++++ python/vyos/ifconfig/vxlan.py | 10 +++++---- smoketest/scripts/cli/test_interfaces_vxlan.py | 29 ++++++++++++++++++++++++++ src/conf_mode/interfaces-vxlan.py | 24 +++++++++++++++++++-- 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 0a8a88596..caeb58116 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -19,6 +19,12 @@ #include #include #include + + + Use external control plane + + + Multicast group address for VXLAN interface diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index d73fb47b8..9615f396d 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -54,18 +54,20 @@ class VXLANIf(Interface): # arguments used by iproute2. For more information please refer to: # - https://man7.org/linux/man-pages/man8/ip-link.8.html mapping = { - 'source_address' : 'local', - 'source_interface' : 'dev', - 'remote' : 'remote', 'group' : 'group', + 'external' : 'external', 'parameters.ip.dont_fragment': 'df set', 'parameters.ip.tos' : 'tos', 'parameters.ip.ttl' : 'ttl', 'parameters.ipv6.flowlabel' : 'flowlabel', 'parameters.nolearning' : 'nolearning', + 'remote' : 'remote', + 'source_address' : 'local', + 'source_interface' : 'dev', + 'vni' : 'id', } - cmd = 'ip link add {ifname} type {type} id {vni} dstport {port}' + cmd = 'ip link add {ifname} type {type} dstport {port}' for vyos_key, iproute2_key in mapping.items(): # dict_search will return an empty dict "{}" for valueless nodes like # "parameters.nolearning" - thus we need to test the nodes existence diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index f63c850d8..17874af89 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -16,6 +16,7 @@ import unittest +from vyos.configsession import ConfigSessionError from vyos.ifconfig import Interface from vyos.util import get_interface_config @@ -78,6 +79,9 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): label = options['linkinfo']['info_data']['label'] self.assertIn(f'parameters ipv6 flowlabel {label}', self._options[interface]) + if any('external' in s for s in self._options[interface]): + self.assertTrue(options['linkinfo']['info_data']['external']) + self.assertEqual('vxlan', options['linkinfo']['info_kind']) self.assertEqual('set', options['linkinfo']['info_data']['df']) self.assertEqual(f'0x{tos}', options['linkinfo']['info_data']['tos']) @@ -85,5 +89,30 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): self.assertEqual(Interface(interface).get_admin_state(), 'up') ttl += 10 + def test_vxlan_external(self): + interface = 'vxlan0' + source_address = '192.0.2.1' + self.cli_set(self._base_path + [interface, 'external']) + self.cli_set(self._base_path + [interface, 'source-address', source_address]) + + # Now add some more interfaces - this must fail and a CLI error needs + # to be generated as Linux can only handle one VXLAN tunnel when using + # external mode. + for intf in self._interfaces: + for option in self._options.get(intf, []): + self.cli_set(self._base_path + [intf] + option.split()) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # Remove those test interfaces again + for intf in self._interfaces: + self.cli_delete(self._base_path + [intf]) + + self.cli_commit() + + options = get_interface_config(interface) + self.assertTrue(options['linkinfo']['info_data']['external']) + self.assertEqual('vxlan', options['linkinfo']['info_kind']) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 804f2d14f..b197d08a6 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -44,6 +44,20 @@ def get_config(config=None): base = ['interfaces', 'vxlan'] vxlan = get_interface_dict(conf, base) + # We need to verify that no other VXLAN tunnel is configured when external + # mode is in use - Linux Kernel limitation + conf.set_level(base) + vxlan['other_tunnels'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # This if-clause is just to be sure - it will always evaluate to true + ifname = vxlan['ifname'] + if ifname in vxlan['other_tunnels']: + del vxlan['other_tunnels'][ifname] + if len(vxlan['other_tunnels']) == 0: + del vxlan['other_tunnels'] + return vxlan def verify(vxlan): @@ -63,8 +77,14 @@ def verify(vxlan): if not any(tmp in ['group', 'remote', 'source_address'] for tmp in vxlan): raise ConfigError('Group, remote or source-address must be configured') - if 'vni' not in vxlan: - raise ConfigError('Must configure VNI for VXLAN') + if 'vni' not in vxlan and 'external' not in vxlan: + raise ConfigError( + 'Must either configure VXLAN "vni" or use "external" CLI option!') + + if {'external', 'other_tunnels'} <= set(vxlan): + other_tunnels = ', '.join(vxlan['other_tunnels']) + raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\ + f'CLI option is used. Additional tunnels: {other_tunnels}') if 'source_interface' in vxlan: # VXLAN adds at least an overhead of 50 byte - we need to check the -- cgit v1.2.3 From 544e73ff26bb0b9d282159e4a734ad531213d816 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 10 Dec 2021 21:15:18 +0100 Subject: vxlan: T3700: can not specify both "external" and "VNI" --- smoketest/scripts/cli/test_interfaces_vxlan.py | 6 ++++++ src/conf_mode/interfaces-vxlan.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index 17874af89..9278adadd 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -95,6 +95,12 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): self.cli_set(self._base_path + [interface, 'external']) self.cli_set(self._base_path + [interface, 'source-address', source_address]) + # Both 'VNI' and 'external' can not be specified at the same time. + self.cli_set(self._base_path + [interface, 'vni', '111']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'vni']) + # Now add some more interfaces - this must fail and a CLI error needs # to be generated as Linux can only handle one VXLAN tunnel when using # external mode. diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index b197d08a6..95d982f38 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -81,6 +81,9 @@ def verify(vxlan): raise ConfigError( 'Must either configure VXLAN "vni" or use "external" CLI option!') + if {'external', 'vni'} <= set(vxlan): + raise ConfigError('Can not specify both "external" and "VNI"!') + if {'external', 'other_tunnels'} <= set(vxlan): other_tunnels = ', '.join(vxlan['other_tunnels']) raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\ -- cgit v1.2.3 From 2daa8aa6892d990d12c50bf16095dac0e1f179c7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 10 Dec 2021 22:10:10 +0100 Subject: wwan: T3795: only run ModemManager when interface is in use (cherry picked from commit a8ebb4817955b3f33f773a4d05c753dfc77958cd) --- src/conf_mode/interfaces-wwan.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index f013e5411..7fdd0d447 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -17,6 +17,7 @@ import os from sys import exit +from time import sleep from vyos.config import Config from vyos.configdict import get_interface_dict @@ -28,10 +29,14 @@ from vyos.util import cmd from vyos.util import call from vyos.util import dict_search from vyos.util import DEVNULL +from vyos.util import is_systemd_service_active +from vyos.template import render from vyos import ConfigError from vyos import airbag airbag.enable() +service_name = 'ModemManager.service' + def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -44,6 +49,20 @@ def get_config(config=None): base = ['interfaces', 'wwan'] wwan = get_interface_dict(conf, base) + # We need to know the amount of other WWAN interfaces as ModemManager needs + # to be started or stopped. + conf.set_level(base) + wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + # This if-clause is just to be sure - it will always evaluate to true + ifname = wwan['ifname'] + if ifname in wwan['other_interfaces']: + del wwan['other_interfaces'][ifname] + if len(wwan['other_interfaces']) == 0: + del wwan['other_interfaces'] + return wwan def verify(wwan): @@ -64,6 +83,18 @@ def generate(wwan): return None def apply(wwan): + if not is_systemd_service_active(service_name): + cmd(f'systemctl start {service_name}') + + counter = 100 + # Wait until a modem is detected and then we can continue + while counter > 0: + counter -= 1 + tmp = cmd('mmcli -L') + if tmp != 'No modems were found': + break + sleep(0.250) + # we only need the modem number. wwan0 -> 0, wwan1 -> 1 modem = wwan['ifname'].lstrip('wwan') base_cmd = f'mmcli --modem {modem}' @@ -73,6 +104,11 @@ def apply(wwan): w = WWANIf(wwan['ifname']) if 'deleted' in wwan or 'disable' in wwan: w.remove() + + # There are no other WWAN interfaces - stop the daemon + if 'other_interfaces' not in wwan: + cmd(f'systemctl stop {service_name}') + return None ip_type = 'ipv4' @@ -93,6 +129,9 @@ def apply(wwan): call(command, stdout=DEVNULL) w.update(wwan) + if 'other_interfaces' not in wwan and 'deleted' in wwan: + cmd(f'systemctl start {service_name}') + return None if __name__ == '__main__': -- cgit v1.2.3 From 03938f718be722a92d26279a5edd822f9261228a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 10 Dec 2021 22:10:37 +0100 Subject: wwan: T3795: only enable cron helper when interface is in use (cherry picked from commit e73b40a04ee90a91b778ce72a60cbb751f42a306) --- src/conf_mode/interfaces-wwan.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index 7fdd0d447..e052e152d 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -30,12 +30,14 @@ from vyos.util import call from vyos.util import dict_search from vyos.util import DEVNULL from vyos.util import is_systemd_service_active +from vyos.util import write_file from vyos.template import render from vyos import ConfigError from vyos import airbag airbag.enable() service_name = 'ModemManager.service' +cron_script = '/etc/cron.d/wwan' def get_config(config=None): """ @@ -80,6 +82,11 @@ def verify(wwan): return None def generate(wwan): + if 'deleted' in wwan: + return None + + if not os.path.exists(cron_script): + write_file(cron_script, '*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py') return None def apply(wwan): @@ -108,6 +115,10 @@ def apply(wwan): # There are no other WWAN interfaces - stop the daemon if 'other_interfaces' not in wwan: cmd(f'systemctl stop {service_name}') + # Clean CRON helper script which is used for to re-connect when + # RF signal is lost + if os.path.exists(cron_script): + os.unlink(cron_script) return None -- cgit v1.2.3 From c2ac56c5fb6a929afe054deab70d9e83a6a7a3f2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 10 Dec 2021 22:17:43 +0100 Subject: wwan: T3795: remove superfluous import (render) (cherry picked from commit 5e7243db4ced47dbad48913f86909ba284fcc24d) --- src/conf_mode/interfaces-wwan.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index e052e152d..a4b033374 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -31,7 +31,6 @@ from vyos.util import dict_search from vyos.util import DEVNULL from vyos.util import is_systemd_service_active from vyos.util import write_file -from vyos.template import render from vyos import ConfigError from vyos import airbag airbag.enable() -- cgit v1.2.3 From 7d47524d1af1edb998fb136908f602d84786f87f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 10 Dec 2021 22:40:14 +0100 Subject: vxlan: T3700: unindent other tunnels cleanup code --- src/conf_mode/interfaces-vxlan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 95d982f38..6cd931049 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -55,8 +55,8 @@ def get_config(config=None): ifname = vxlan['ifname'] if ifname in vxlan['other_tunnels']: del vxlan['other_tunnels'][ifname] - if len(vxlan['other_tunnels']) == 0: - del vxlan['other_tunnels'] + if len(vxlan['other_tunnels']) == 0: + del vxlan['other_tunnels'] return vxlan -- cgit v1.2.3 From 7670dd86d9ba4bbf9a5b47b8ad72cf3c8537859f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 11 Dec 2021 10:07:42 +0100 Subject: bfd: T3310: bugfix on profile names using hyphens --- smoketest/scripts/cli/test_protocols_bfd.py | 27 ++++++++++++++++++--------- src/conf_mode/protocols_bfd.py | 3 ++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py index d234750b4..926c3eada 100755 --- a/smoketest/scripts/cli/test_protocols_bfd.py +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -31,7 +31,8 @@ peers = { 'intv_tx' : '600', 'multihop' : '', 'source_addr': '192.0.2.254', - }, + 'profile' : 'foo-bar-baz', + }, '192.0.2.20' : { 'echo_mode' : '', 'intv_echo' : '100', @@ -42,15 +43,16 @@ peers = { 'shutdown' : '', 'profile' : 'foo', 'source_intf': dum_if, - }, - '2001:db8::a' : { + }, + '2001:db8::1000:1' : { 'source_addr': '2001:db8::1', 'vrf' : vrf_name, - }, - '2001:db8::b' : { + }, + '2001:db8::2000:1' : { 'source_addr': '2001:db8::1', 'multihop' : '', - }, + 'profile' : 'baz_foo', + }, } profiles = { @@ -62,7 +64,12 @@ profiles = { 'intv_tx' : '333', 'shutdown' : '', }, - 'bar' : { + 'foo-bar-baz' : { + 'intv_mult' : '4', + 'intv_rx' : '400', + 'intv_tx' : '400', + }, + 'baz_foo' : { 'intv_mult' : '102', 'intv_rx' : '444', 'passive' : '', @@ -143,8 +150,6 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.cli_delete(['vrf', 'name', vrf_name]) def test_bfd_profile(self): - peer = '192.0.2.10' - for profile, profile_config in profiles.items(): if 'echo_mode' in profile_config: self.cli_set(base_path + ['profile', profile, 'echo-mode']) @@ -164,6 +169,10 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): for peer, peer_config in peers.items(): if 'profile' in peer_config: self.cli_set(base_path + ['peer', peer, 'profile', peer_config["profile"] + 'wrong']) + if 'source_addr' in peer_config: + self.cli_set(base_path + ['peer', peer, 'source', 'address', peer_config["source_addr"]]) + if 'source_intf' in peer_config: + self.cli_set(base_path + ['peer', peer, 'source', 'interface', peer_config["source_intf"]]) # BFD profile does not exist! with self.assertRaises(ConfigSessionError): diff --git a/src/conf_mode/protocols_bfd.py b/src/conf_mode/protocols_bfd.py index 8593da170..4ebc0989c 100755 --- a/src/conf_mode/protocols_bfd.py +++ b/src/conf_mode/protocols_bfd.py @@ -35,7 +35,8 @@ def get_config(config=None): conf = Config() base = ['protocols', 'bfd'] bfd = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True) + get_first_key=True, + no_tag_node_value_mangle=True) # Bail out early if configuration tree does not exist if not conf.exists(base): return bfd -- cgit v1.2.3 From 1a5370cca082b283de8a15e2485eedbd10fea8da Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 11 Dec 2021 10:08:26 +0100 Subject: smoketest: bfd: only read in FRR configuration from bfdd --- smoketest/scripts/cli/test_protocols_bfd.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py index 926c3eada..fdc254a05 100755 --- a/smoketest/scripts/cli/test_protocols_bfd.py +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -114,7 +114,7 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig('bfd') + frrconfig = self.getFRRconfig('bfd', daemon=PROCESS_NAME) for peer, peer_config in peers.items(): tmp = f'peer {peer}' if 'multihop' in peer_config: @@ -127,7 +127,7 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): tmp += f' vrf {peer_config["vrf"]}' self.assertIn(tmp, frrconfig) - peerconfig = self.getFRRconfig(f' peer {peer}', end='') + peerconfig = self.getFRRconfig(f' peer {peer}', end='', daemon=PROCESS_NAME) if 'echo_mode' in peer_config: self.assertIn(f'echo-mode', peerconfig) @@ -206,7 +206,7 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.assertNotIn(f'shutdown', config) for peer, peer_config in peers.items(): - peerconfig = self.getFRRconfig(f' peer {peer}', end='') + peerconfig = self.getFRRconfig(f' peer {peer}', end='', daemon=PROCESS_NAME) if 'profile' in peer_config: self.assertIn(f' profile {peer_config["profile"]}', peerconfig) -- cgit v1.2.3 From 86ab3b7dad7f2ad2c39a8b110e4a845195cda32e Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sat, 11 Dec 2021 12:47:26 -0600 Subject: vyos.util: T4061: fix typo in function name --- src/services/vyos-configd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/vyos-configd b/src/services/vyos-configd index 2d18589ef..48c9135e2 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -28,7 +28,7 @@ import zmq from contextlib import contextmanager from vyos.defaults import directories -from vyos.util import boot_config_complete +from vyos.util import boot_configuration_complete from vyos.configsource import ConfigSourceString, ConfigSourceError from vyos.config import Config from vyos import ConfigError @@ -187,7 +187,7 @@ def initialization(socket): session_out = None # if not a 'live' session, for example on boot, write to file - if not session_out or not boot_config_complete(): + if not session_out or not boot_configuration_complete(): session_out = script_stdout_log session_mode = 'a' -- cgit v1.2.3 From 9ccc353893a3a9a1dc7dfd59463d34449bf05afb Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 11 Dec 2021 20:47:23 +0100 Subject: T3912: migrate "Welcome to VyOS" from issue file to motd to not silently expose OS --- src/conf_mode/system-login-banner.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/conf_mode/system-login-banner.py b/src/conf_mode/system-login-banner.py index 2220d7b66..9642b2aae 100755 --- a/src/conf_mode/system-login-banner.py +++ b/src/conf_mode/system-login-banner.py @@ -15,13 +15,16 @@ # along with this program. If not, see . from sys import exit + from vyos.config import Config +from vyos.util import write_file from vyos import ConfigError - from vyos import airbag airbag.enable() motd=""" +Welcome to VyOS + Check out project news at https://blog.vyos.io and feel free to report bugs at https://phabricator.vyos.net @@ -38,7 +41,7 @@ POSTLOGIN_FILE = r'/etc/motd' default_config_data = { 'issue': 'Welcome to VyOS - \\n \\l\n\n', - 'issue_net': 'Welcome to VyOS\n', + 'issue_net': '', 'motd': motd } @@ -92,14 +95,9 @@ def generate(banner): pass def apply(banner): - with open(PRELOGIN_FILE, 'w') as f: - f.write(banner['issue']) - - with open(PRELOGIN_NET_FILE, 'w') as f: - f.write(banner['issue_net']) - - with open(POSTLOGIN_FILE, 'w') as f: - f.write(banner['motd']) + write_file(PRELOGIN_FILE, banner['issue']) + write_file(PRELOGIN_NET_FILE, banner['issue_net']) + write_file(POSTLOGIN_FILE, banner['motd']) return None -- cgit v1.2.3 From 6a8026e1f9a18f4c3e34b376ecce58e5225ac2b3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Dec 2021 10:07:16 +0100 Subject: xml: bgp: fix "shutdown" help string (remove whitespace) --- interface-definitions/include/bgp/neighbor-shutdown.xml.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface-definitions/include/bgp/neighbor-shutdown.xml.i b/interface-definitions/include/bgp/neighbor-shutdown.xml.i index 6d15899a6..acc7bc5a9 100644 --- a/interface-definitions/include/bgp/neighbor-shutdown.xml.i +++ b/interface-definitions/include/bgp/neighbor-shutdown.xml.i @@ -1,7 +1,7 @@ - Administratively shut down this neighbor + Administratively shutdown this neighbor -- cgit v1.2.3 From ede5ba7c1e9e5e2862f91d3082b50b71a1ffe920 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Dec 2021 09:56:46 +0100 Subject: bgp: T3967: add "parameters conditional-advertisement timer " option Set the period to rerun the conditional advertisement scanner process. The default is 60 seconds. --- data/templates/frr/bgpd.frr.tmpl | 5 +++++ .../include/bgp/protocol-common-config.xml.i | 20 ++++++++++++++++++++ smoketest/scripts/cli/test_protocols_bgp.py | 4 ++++ 3 files changed, 29 insertions(+) diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index a3c6431f6..cae53d09c 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -475,6 +475,11 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% if parameters.cluster_id is defined and parameters.cluster_id is not none %} bgp cluster-id {{ parameters.cluster_id }} {% endif %} +{% if parameters.conditional_advertisement is defined and parameters.conditional_advertisement is not none %} +{% if parameters.conditional_advertisement.timer is defined and parameters.conditional_advertisement.timer is not none %} + bgp conditional-advertisement timer {{ parameters.conditional_advertisement.timer }} +{% endif %} +{% endif %} {% if parameters.confederation is defined and parameters.confederation is not none %} {% if parameters.confederation.identifier is defined and parameters.confederation.identifier is not none %} bgp confederation identifier {{ parameters.confederation.identifier }} diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 2dfae517e..351ee8eda 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1181,6 +1181,26 @@ + + + Conditional advertisement settings + + + + + Set period to rescan BGP table to check if condition is met + + u32:5-240 + Period to rerun the conditional advertisement scanner process (default: 60) + + + + + + 60 + + + Enable route-flap dampening diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 5fdca4fc2..8282d507a 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -218,6 +218,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): max_path_v4ibgp = '4' max_path_v6 = '8' max_path_v6ibgp = '16' + cond_adv_timer = '30' self.cli_set(base_path + ['parameters', 'router-id', router_id]) self.cli_set(base_path + ['parameters', 'log-neighbor-changes']) @@ -239,6 +240,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['parameters', 'bestpath', 'bandwidth', 'default-weight-for-missing']) self.cli_set(base_path + ['parameters', 'bestpath', 'compare-routerid']) + self.cli_set(base_path + ['parameters', 'conditional-advertisement', 'timer', cond_adv_timer]) + # AFI maximum path support self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4]) self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ibgp', max_path_v4ibgp]) @@ -254,6 +257,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' bgp router-id {router_id}', frrconfig) self.assertIn(f' bgp log-neighbor-changes', frrconfig) self.assertIn(f' bgp default local-preference {local_pref}', frrconfig) + self.assertIn(f' bgp conditional-advertisement timer {cond_adv_timer}', frrconfig) self.assertIn(f' bgp graceful-restart stalepath-time {stalepath_time}', frrconfig) self.assertIn(f' bgp graceful-shutdown', frrconfig) self.assertIn(f' bgp bestpath as-path multipath-relax', frrconfig) -- cgit v1.2.3 From ee2c84b2f0cc13d71122bd2ee0640bb13aa8040e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Dec 2021 10:06:17 +0100 Subject: bgp: T4069: add "parameters fast-convergence" CLI option Whenever BGP peer address becomes unreachable we must bring down the BGP session immediately. Currently only single-hop EBGP sessions are brought down immediately. IBGP and multi-hop EBGP sessions wait for hold-timer expiry to bring down the sessions. This new configuration option helps user to teardown BGP sessions immediately whenever peer becomes unreachable. This configuration is available at the bgp level. When enabled, configuration is applied to all the neighbors configured in that bgp instance. --- data/templates/frr/bgpd.frr.tmpl | 3 +++ interface-definitions/include/bgp/protocol-common-config.xml.i | 6 ++++++ smoketest/scripts/cli/test_protocols_bgp.py | 2 ++ 3 files changed, 11 insertions(+) diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index cae53d09c..351e4a7ed 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -510,6 +510,9 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% endfor %} {% endif %} {% endif %} +{% if parameters.fast_convergence is defined %} + bgp fast-convergence +{% endif %} {% if parameters.graceful_restart is defined %} bgp graceful-restart {{ 'stalepath-time ' ~ parameters.graceful_restart.stalepath_time if parameters.graceful_restart.stalepath_time is defined }} {% endif %} diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 351ee8eda..5dd4e522b 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1363,6 +1363,12 @@ + + + Teardown sessions immediately whenever peer becomes unreachable + + + Graceful restart capability parameters diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 8282d507a..2693ca5ba 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -241,6 +241,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['parameters', 'bestpath', 'compare-routerid']) self.cli_set(base_path + ['parameters', 'conditional-advertisement', 'timer', cond_adv_timer]) + self.cli_set(base_path + ['parameters', 'fast-convergence']) # AFI maximum path support self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4]) @@ -258,6 +259,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' bgp log-neighbor-changes', frrconfig) self.assertIn(f' bgp default local-preference {local_pref}', frrconfig) self.assertIn(f' bgp conditional-advertisement timer {cond_adv_timer}', frrconfig) + self.assertIn(f' bgp fast-convergence', frrconfig) self.assertIn(f' bgp graceful-restart stalepath-time {stalepath_time}', frrconfig) self.assertIn(f' bgp graceful-shutdown', frrconfig) self.assertIn(f' bgp bestpath as-path multipath-relax', frrconfig) -- cgit v1.2.3 From 1d4c23ca9b62dfa6db6580440b21cd89c5d8f7e8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Dec 2021 10:06:57 +0100 Subject: bgp: T4069: add "parameters minimum-holdtime " CLI option This command allows user to prevent session establishment with BGP peers with lower holdtime less than configured minimum holdtime. When this command is not set, minimum holdtime does not work. --- data/templates/frr/bgpd.frr.tmpl | 3 +++ .../include/bgp/protocol-common-config.xml.i | 12 ++++++++++++ smoketest/scripts/cli/test_protocols_bgp.py | 3 +++ 3 files changed, 18 insertions(+) diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index 351e4a7ed..c04475070 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -522,6 +522,9 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% if parameters.log_neighbor_changes is defined %} bgp log-neighbor-changes {% endif %} +{% if parameters.minimum_holdtime is defined and parameters.minimum_holdtime is not none %} + bgp minimum-holdtime {{ parameters.minimum_holdtime }} +{% endif %} {% if parameters.network_import_check is defined %} bgp network import-check {% endif %} diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 5dd4e522b..20ac59c5b 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1400,6 +1400,18 @@ + + + BGP minimum holdtime + + u32:1-65535 + Minimum holdtime in seconds + + + + + + Enable IGP route check for network statements diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 2693ca5ba..983f6ecd3 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -219,6 +219,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): max_path_v6 = '8' max_path_v6ibgp = '16' cond_adv_timer = '30' + min_hold_time = '2' self.cli_set(base_path + ['parameters', 'router-id', router_id]) self.cli_set(base_path + ['parameters', 'log-neighbor-changes']) @@ -242,6 +243,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['parameters', 'conditional-advertisement', 'timer', cond_adv_timer]) self.cli_set(base_path + ['parameters', 'fast-convergence']) + self.cli_set(base_path + ['parameters', 'minimum-holdtime', min_hold_time]) # AFI maximum path support self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4]) @@ -265,6 +267,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' bgp bestpath as-path multipath-relax', frrconfig) self.assertIn(f' bgp bestpath bandwidth default-weight-for-missing', frrconfig) self.assertIn(f' bgp bestpath compare-routerid', frrconfig) + self.assertIn(f' bgp minimum-holdtime {min_hold_time}', frrconfig) self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) afiv4_config = self.getFRRconfig(' address-family ipv4 unicast') -- cgit v1.2.3 From bddabbf696ec8ff638b93c5260aa4823dcc43df3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Dec 2021 10:09:24 +0100 Subject: bgp: T4069: add "parameters reject-as-sets" CLI option This command enables rejection of incoming and outgoing routes having AS_SET or AS_CONFED_SET type. --- data/templates/frr/bgpd.frr.tmpl | 3 +++ interface-definitions/include/bgp/protocol-common-config.xml.i | 6 ++++++ smoketest/scripts/cli/test_protocols_bgp.py | 2 ++ 3 files changed, 11 insertions(+) diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index c04475070..e49aaf12c 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -534,6 +534,9 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% if parameters.no_fast_external_failover is defined %} no bgp fast-external-failover {% endif %} +{% if parameters.reject_as_sets is defined %} + bgp reject-as-sets +{% endif %} {% if parameters.router_id is defined and parameters.router_id is not none %} bgp router-id {{ parameters.router_id }} {% endif %} diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 20ac59c5b..82c8e55a0 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1430,6 +1430,12 @@ + + + Reject routes with AS_SET or AS_CONFED_SET flag + + + #include diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 983f6ecd3..87673f459 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -244,6 +244,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['parameters', 'conditional-advertisement', 'timer', cond_adv_timer]) self.cli_set(base_path + ['parameters', 'fast-convergence']) self.cli_set(base_path + ['parameters', 'minimum-holdtime', min_hold_time]) + self.cli_set(base_path + ['parameters', 'reject-as-sets']) # AFI maximum path support self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4]) @@ -268,6 +269,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' bgp bestpath bandwidth default-weight-for-missing', frrconfig) self.assertIn(f' bgp bestpath compare-routerid', frrconfig) self.assertIn(f' bgp minimum-holdtime {min_hold_time}', frrconfig) + self.assertIn(f' bgp reject-as-sets', frrconfig) self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) afiv4_config = self.getFRRconfig(' address-family ipv4 unicast') -- cgit v1.2.3 From aca9d98c818407b8009bb861ec0f5a817a9a5637 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Dec 2021 10:10:10 +0100 Subject: bgp: T4069: add "parameters shutdown" CLI option Administrative shutdown of all peers of a bgp instance. Drop all BGP peers, but preserve their configurations. The peers are notified in accordance with RFC 8203 by sending a NOTIFICATION message with error code Cease and subcode Administrative Shutdown prior to terminating connections. This global shutdown is independent of the neighbor shutdown, meaning that individually shut down peers will not be affected by lifting it. --- data/templates/frr/bgpd.frr.tmpl | 3 +++ interface-definitions/include/bgp/protocol-common-config.xml.i | 6 ++++++ smoketest/scripts/cli/test_protocols_bgp.py | 2 ++ 3 files changed, 11 insertions(+) diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index e49aaf12c..01e62a94b 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -540,6 +540,9 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% if parameters.router_id is defined and parameters.router_id is not none %} bgp router-id {{ parameters.router_id }} {% endif %} +{% if parameters.shutdown is defined %} + bgp shutdown +{% endif %} {% endif %} {% if timers is defined and timers.keepalive is defined and timers.holdtime is defined %} timers bgp {{ timers.keepalive }} {{ timers.holdtime }} diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 82c8e55a0..2b2d6fa82 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1436,6 +1436,12 @@ + + + Administrative shutdown of the BGP instance + + + #include diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 87673f459..74aff71eb 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -245,6 +245,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['parameters', 'fast-convergence']) self.cli_set(base_path + ['parameters', 'minimum-holdtime', min_hold_time]) self.cli_set(base_path + ['parameters', 'reject-as-sets']) + self.cli_set(base_path + ['parameters', 'shutdown']) # AFI maximum path support self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4]) @@ -270,6 +271,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' bgp bestpath compare-routerid', frrconfig) self.assertIn(f' bgp minimum-holdtime {min_hold_time}', frrconfig) self.assertIn(f' bgp reject-as-sets', frrconfig) + self.assertIn(f' bgp shutdown', frrconfig) self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) afiv4_config = self.getFRRconfig(' address-family ipv4 unicast') -- cgit v1.2.3 From 6fab523565015fd4da5d7295bad684cb969e5d12 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Dec 2021 10:11:49 +0100 Subject: bgp: T4069: add "parameters suppress-fib-pending" CLI option This command is applicable at the global level and at an individual bgp level. If applied at the global level all bgp instances will wait for fib installation before announcing routes and there is no way to turn it off for a particular BGP vrf. --- data/templates/frr/bgpd.frr.tmpl | 5 ++++- interface-definitions/include/bgp/protocol-common-config.xml.i | 8 +++++++- smoketest/scripts/cli/test_protocols_bgp.py | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index 01e62a94b..9bb42fd2d 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -543,8 +543,11 @@ router bgp {{ local_as }} {{ 'vrf ' ~ vrf if vrf is defined and vrf is not none {% if parameters.shutdown is defined %} bgp shutdown {% endif %} +{% if parameters.suppress_fib_pending is defined %} + bgp suppress-fib-pending +{% endif %} {% endif %} {% if timers is defined and timers.keepalive is defined and timers.holdtime is defined %} timers bgp {{ timers.keepalive }} {{ timers.holdtime }} {% endif %} -exit \ No newline at end of file +exit diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 2b2d6fa82..8214d0779 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -1442,6 +1442,12 @@ + + + Advertise only routes that are programmed in kernel to peers + + + #include @@ -1491,4 +1497,4 @@ #include - \ No newline at end of file + diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 74aff71eb..a2bcd0685 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -246,6 +246,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['parameters', 'minimum-holdtime', min_hold_time]) self.cli_set(base_path + ['parameters', 'reject-as-sets']) self.cli_set(base_path + ['parameters', 'shutdown']) + self.cli_set(base_path + ['parameters', 'suppress-fib-pending']) # AFI maximum path support self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4]) @@ -272,6 +273,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' bgp minimum-holdtime {min_hold_time}', frrconfig) self.assertIn(f' bgp reject-as-sets', frrconfig) self.assertIn(f' bgp shutdown', frrconfig) + self.assertIn(f' bgp suppress-fib-pending', frrconfig) self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) afiv4_config = self.getFRRconfig(' address-family ipv4 unicast') @@ -786,4 +788,4 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' exit-address-family', afi_config) if __name__ == '__main__': - unittest.main(verbosity=2) \ No newline at end of file + unittest.main(verbosity=2) -- cgit v1.2.3 From ab27bb6edd44e5ab49c1bab28c9a6bc2cafb2e36 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Dec 2021 13:44:20 +0100 Subject: bgp: smoketest: add proper peer-group assignment tests In the past a peer-group was only assigned to the BGP process but not bound to any neighbor. This has been changed. --- smoketest/scripts/cli/test_protocols_bgp.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index a2bcd0685..22cc3785f 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -69,6 +69,7 @@ neighbor_config = { 'passive' : '', 'multi_hop' : '5', 'update_src' : 'lo', + 'peer_group' : 'foo', }, '2001:db8::1' : { 'cap_dynamic' : '', @@ -86,6 +87,7 @@ neighbor_config = { 'route_map_out': route_map_out, 'no_send_comm_std' : '', 'addpath_per_as' : '', + 'peer_group' : 'foo-bar', }, '2001:db8::2' : { 'remote_as' : '456', @@ -96,6 +98,7 @@ neighbor_config = { 'pfx_list_in' : prefix_list_in6, 'pfx_list_out' : prefix_list_out6, 'no_send_comm_ext' : '', + 'peer_group' : 'foo-bar_baz', }, } @@ -109,7 +112,7 @@ peer_group_config = { 'cap_over' : '', 'ttl_security': '5', }, - 'bar' : { + 'foo-bar' : { 'description' : 'foo peer bar group', 'remote_as' : '200', 'shutdown' : '', @@ -120,6 +123,7 @@ peer_group_config = { 'no_send_comm_ext' : '', }, 'baz' : { + 'foo-bar_baz' : { 'bfd_profile' : bfd_profile, 'cap_dynamic' : '', 'cap_ext_next' : '', @@ -417,6 +421,10 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'addpath_per_as' in config: self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'addpath-tx-per-as']) + for peer, peer_config in neighbor_config.items(): + if 'peer_group' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'peer-group', peer_config['peer_group']]) + # commit changes self.cli_commit() @@ -428,6 +436,10 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) self.verify_frr_config(peer, peer_config, frrconfig) + for peer, peer_config in neighbor_config.items(): + if 'peer_group' in peer_config: + self.assertIn(f' neighbor {peer} peer-group {peer_config["peer_group"]}', frrconfig) + def test_bgp_04_afi_ipv4(self): networks = { -- cgit v1.2.3 From c233c7e6d058f31283d7d0f2f924c35cd148ee5f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Dec 2021 10:28:06 +0100 Subject: xml: bgp: rename afi-common.xml.i -> neighbor-afi-ipv4-ipv6-common.xml.i --- interface-definitions/include/bgp/afi-common.xml.i | 142 --------------------- .../bgp/neighbor-afi-ipv4-ipv6-common.xml.i | 142 +++++++++++++++++++++ .../bgp/neighbor-afi-ipv4-labeled-unicast.xml.i | 2 +- .../include/bgp/neighbor-afi-ipv4-multicast.xml.i | 2 +- .../include/bgp/neighbor-afi-ipv4-unicast.xml.i | 2 +- .../include/bgp/neighbor-afi-ipv4-vpn.xml.i | 2 +- .../bgp/neighbor-afi-ipv6-labeled-unicast.xml.i | 2 +- .../include/bgp/neighbor-afi-ipv6-multicast.xml.i | 2 +- .../include/bgp/neighbor-afi-ipv6-unicast.xml.i | 2 +- .../include/bgp/neighbor-afi-ipv6-vpn.xml.i | 2 +- 10 files changed, 150 insertions(+), 150 deletions(-) delete mode 100644 interface-definitions/include/bgp/afi-common.xml.i create mode 100644 interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i diff --git a/interface-definitions/include/bgp/afi-common.xml.i b/interface-definitions/include/bgp/afi-common.xml.i deleted file mode 100644 index 62beff40c..000000000 --- a/interface-definitions/include/bgp/afi-common.xml.i +++ /dev/null @@ -1,142 +0,0 @@ - - - - Use addpath to advertise all paths to a neighbor - - - - - - Use addpath to advertise the bestpath per each neighboring AS - - - -#include - - - AS for routes sent to this peer to be the local AS - - - -#include - - - Disable sending community attributes to this peer - - - - - Disable sending extended community attributes to this peer - - - - - - Disable sending standard community attributes to this peer - - - - - - - - Access-list to filter route updates to/from this peer-group - - - - - Access-list to filter outgoing route updates to this peer-group - - policy access-list - - - u32:1-65535 - Access-list to filter outgoing route updates to this peer-group - - - - - - - - - Access-list to filter incoming route updates from this peer-group - - policy access-list - - - u32:1-65535 - Access-list to filter incoming route updates from this peer-group - - - - - - - - -#include - - - Maximum number of prefixes to accept from this peer - - u32:1-4294967295 - Prefix limit - - - - - - - - - Maximum number of prefixes to be sent to this peer - - u32:1-4294967295 - Prefix limit - - - - - - -#include - - - Remove private AS numbers from AS path in outbound route updates - - - -#include -#include -#include -#include - - - Route-map to selectively unsuppress suppressed routes - - policy route-map - - - txt - Route map name - - - ^[-_a-zA-Z0-9.]+$ - - Name of route-map can only contain alpha-numeric letters, hyphen and underscores - - - - - Default weight for routes from this peer - - u32:1-65535 - Default weight - - - - - - - diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i new file mode 100644 index 000000000..498662b0a --- /dev/null +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i @@ -0,0 +1,142 @@ + + + + Use addpath to advertise all paths to a neighbor + + + + + + Use addpath to advertise the bestpath per each neighboring AS + + + +#include + + + AS for routes sent to this peer to be the local AS + + + +#include + + + Disable sending community attributes to this peer + + + + + Disable sending extended community attributes to this peer + + + + + + Disable sending standard community attributes to this peer + + + + + + + + Access-list to filter route updates to/from this peer-group + + + + + Access-list to filter outgoing route updates to this peer-group + + policy access-list + + + u32:1-65535 + Access-list to filter outgoing route updates to this peer-group + + + + + + + + + Access-list to filter incoming route updates from this peer-group + + policy access-list + + + u32:1-65535 + Access-list to filter incoming route updates from this peer-group + + + + + + + + +#include + + + Maximum number of prefixes to accept from this peer + + u32:1-4294967295 + Prefix limit + + + + + + + + + Maximum number of prefixes to be sent to this peer + + u32:1-4294967295 + Prefix limit + + + + + + +#include + + + Remove private AS numbers from AS path in outbound route updates + + + +#include +#include +#include +#include + + + Route-map to selectively unsuppress suppressed routes + + policy route-map + + + txt + Route map name + + + ^[-_a-zA-Z0-9.]+$ + + Name of route-map can only contain alpha-numeric letters, hyphen and underscores + + + + + Default weight for routes from this peer + + u32:1-65535 + Default weight + + + + + + + diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i index 45a440fd8..0eae29f5e 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-labeled-unicast.xml.i @@ -13,7 +13,7 @@ #include - #include + #include #include diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-multicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-multicast.xml.i index 6526169ca..4bb6df7c3 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv4-multicast.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-multicast.xml.i @@ -13,7 +13,7 @@ #include - #include + #include #include diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-unicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-unicast.xml.i index b7b7ca5b5..0094ce874 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv4-unicast.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-unicast.xml.i @@ -13,7 +13,7 @@ #include - #include + #include #include diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-vpn.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-vpn.xml.i index 838327bc9..220f22fe3 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv4-vpn.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-vpn.xml.i @@ -5,7 +5,7 @@ #include - #include + #include diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i index f680b7357..995183571 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-labeled-unicast.xml.i @@ -14,7 +14,7 @@ #include #include - #include + #include #include diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-multicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-multicast.xml.i index 1f8db8361..bb713c313 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv6-multicast.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-multicast.xml.i @@ -6,7 +6,7 @@ #include #include - #include + #include #include diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-unicast.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-unicast.xml.i index f6b812c28..26a5e7090 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv6-unicast.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-unicast.xml.i @@ -14,7 +14,7 @@ #include #include - #include + #include #include diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv6-vpn.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv6-vpn.xml.i index c0df71cf3..5c6811986 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv6-vpn.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv6-vpn.xml.i @@ -6,7 +6,7 @@ #include #include - #include + #include -- cgit v1.2.3 From ebccc291865132d2dd03edd2a56d400dd087ef43 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Dec 2021 12:29:46 +0100 Subject: bgp: T3967: add support for conditional advertisement The BGP conditional advertisement feature uses the non-exist-map or the exist-map and the advertise-map keywords of the neighbor advertise-map command in order to track routes by the route prefix. non-exist-map ============= * If a route prefix is not present in the output of non-exist-map command, then advertise the route specified by the advertise-map command. * If a route prefix is present in the output of non-exist-map command, then do not advertise the route specified by the addvertise-map command. exist-map ========= * If a route prefix is present in the output of exist-map command, then advertise the route specified by the advertise-map command. * If a route prefix is not present in the output of exist-map command, then do not advertise the route specified by the advertise-map command. This feature is useful when some prefixes are advertised to one of its peers only if the information from the other peer is not present (due to failure in peering session or partial reachability etc). The conditional BGP announcements are sent in addition to the normal announcements that a BGP router sends to its peer. CLI nodes can be found under: * set protocols bgp neighbor address-family conditional-advertisement * set protocols bgp peer-group

address-family conditional-advertisement --- data/templates/frr/bgpd.frr.tmpl | 11 +++ .../bgp/neighbor-afi-ipv4-ipv6-common.xml.i | 55 ++++++++++++++ smoketest/scripts/cli/test_protocols_bgp.py | 84 ++++++++++++++++++---- src/conf_mode/protocols_bgp.py | 22 ++++++ 4 files changed, 157 insertions(+), 15 deletions(-) diff --git a/data/templates/frr/bgpd.frr.tmpl b/data/templates/frr/bgpd.frr.tmpl index 9bb42fd2d..45e0544b7 100644 --- a/data/templates/frr/bgpd.frr.tmpl +++ b/data/templates/frr/bgpd.frr.tmpl @@ -146,6 +146,17 @@ {% if afi_config.as_override is defined %} neighbor {{ neighbor }} as-override {% endif %} +{% if afi_config.conditionally_advertise is defined and afi_config.conditionally_advertise is not none %} +{% if afi_config.conditionally_advertise.advertise_map is defined and afi_config.conditionally_advertise.advertise_map is not none %} +{% set exist_non_exist_map = 'exist-map' %} +{% if afi_config.conditionally_advertise.exist_map is defined and afi_config.conditionally_advertise.exist_map is not none %} +{% set exist_non_exist_map = 'exist-map ' ~ afi_config.conditionally_advertise.exist_map %} +{% elif afi_config.conditionally_advertise.non_exist_map is defined and afi_config.conditionally_advertise.non_exist_map is not none %} +{% set exist_non_exist_map = 'non-exist-map ' ~ afi_config.conditionally_advertise.non_exist_map %} +{% endif %} + neighbor {{ neighbor }} advertise-map {{ afi_config.conditionally_advertise.advertise_map }} {{ exist_non_exist_map }} +{% endif %} +{% endif %} {% if afi_config.remove_private_as is defined %} neighbor {{ neighbor }} remove-private-AS {% endif %} diff --git a/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i index 498662b0a..f3fc4444c 100644 --- a/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i +++ b/interface-definitions/include/bgp/neighbor-afi-ipv4-ipv6-common.xml.i @@ -11,6 +11,61 @@ + + + Use route-map to conditionally advertise routes + + + + + Route-map to conditionally advertise routes + + policy route-map + + + txt + Route map name + + + ^[-_a-zA-Z0-9.]+$ + + Name of route-map can only contain alpha-numeric letters, hyphen and underscores + + + + + Advertise routes only if prefixes in exist-map are installed in BGP table + + policy route-map + + + txt + Route map name + + + ^[-_a-zA-Z0-9.]+$ + + Name of route-map can only contain alpha-numeric letters, hyphen and underscores + + + + + Advertise routes only if prefixes in non-exist-map are not installed in BGP table + + policy route-map + + + txt + Route map name + + + ^[-_a-zA-Z0-9.]+$ + + Name of route-map can only contain alpha-numeric letters, hyphen and underscores + + + + #include diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 22cc3785f..d7230baf4 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -59,11 +59,14 @@ neighbor_config = { 'no_cap_nego' : '', 'port' : '667', 'cap_strict' : '', + 'advertise_map': route_map_in, + 'non_exist_map': route_map_out, 'pfx_list_in' : prefix_list_in, 'pfx_list_out' : prefix_list_out, 'no_send_comm_std' : '', }, '192.0.2.3' : { + 'advertise_map': route_map_in, 'description' : 'foo bar baz', 'remote_as' : '200', 'passive' : '', @@ -72,6 +75,8 @@ neighbor_config = { 'peer_group' : 'foo', }, '2001:db8::1' : { + 'advertise_map': route_map_in, + 'exist_map' : route_map_out, 'cap_dynamic' : '', 'cap_ext_next' : '', 'remote_as' : '123', @@ -104,6 +109,8 @@ neighbor_config = { peer_group_config = { 'foo' : { + 'advertise_map': route_map_in, + 'exist_map' : route_map_out, 'bfd' : '', 'remote_as' : '100', 'passive' : '', @@ -113,6 +120,7 @@ peer_group_config = { 'ttl_security': '5', }, 'foo-bar' : { + 'advertise_map': route_map_in, 'description' : 'foo peer bar group', 'remote_as' : '200', 'shutdown' : '', @@ -122,8 +130,9 @@ peer_group_config = { 'pfx_list_out' : prefix_list_out, 'no_send_comm_ext' : '', }, - 'baz' : { 'foo-bar_baz' : { + 'advertise_map': route_map_in, + 'non_exist_map': route_map_out, 'bfd_profile' : bfd_profile, 'cap_dynamic' : '', 'cap_ext_next' : '', @@ -137,23 +146,34 @@ peer_group_config = { } class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self.cli_set(['policy', 'route-map', route_map_in, 'rule', '10', 'action', 'permit']) - self.cli_set(['policy', 'route-map', route_map_out, 'rule', '10', 'action', 'permit']) - self.cli_set(['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'action', 'permit']) - self.cli_set(['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'prefix', '192.0.2.0/25']) - self.cli_set(['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'action', 'permit']) - self.cli_set(['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'prefix', '192.0.2.128/25']) - - self.cli_set(['policy', 'prefix-list6', prefix_list_in6, 'rule', '10', 'action', 'permit']) - self.cli_set(['policy', 'prefix-list6', prefix_list_in6, 'rule', '10', 'prefix', '2001:db8:1000::/64']) - self.cli_set(['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'action', 'deny']) - self.cli_set(['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'prefix', '2001:db8:2000::/64']) + @classmethod + def setUpClass(cls): + super(cls, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, ['policy', 'route-map', route_map_in, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'route-map', route_map_out, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_in, 'rule', '10', 'prefix', '192.0.2.0/25']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list', prefix_list_out, 'rule', '10', 'prefix', '192.0.2.128/25']) + + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_in6, 'rule', '10', 'action', 'permit']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_in6, 'rule', '10', 'prefix', '2001:db8:1000::/64']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'action', 'deny']) + cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'prefix', '2001:db8:2000::/64']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, ['policy']) + def setUp(self): self.cli_set(base_path + ['local-as', ASN]) def tearDown(self): - self.cli_delete(['policy']) self.cli_delete(['vrf']) self.cli_delete(base_path) self.cli_commit() @@ -212,7 +232,13 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {peer} addpath-tx-all-paths', frrconfig) if 'addpath_per_as' in peer_config: self.assertIn(f' neighbor {peer} addpath-tx-bestpath-per-AS', frrconfig) - + if 'advertise_map' in peer_config: + base = f' neighbor {peer} advertise-map {peer_config["advertise_map"]}' + if 'exist_map' in peer_config: + base = f'{base} exist-map {peer_config["exist_map"]}' + if 'non_exist_map' in peer_config: + base = f'{base} non-exist-map {peer_config["non_exist_map"]}' + self.assertIn(base, frrconfig) def test_bgp_01_simple(self): router_id = '127.0.0.1' @@ -353,6 +379,20 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'addpath_per_as' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'addpath-tx-per-as']) + # Conditional advertisement + if 'advertise_map' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'conditionally-advertise', 'advertise-map', peer_config["advertise_map"]]) + # Either exist-map or non-exist-map needs to be specified + if 'exist_map' not in peer_config and 'non_exist_map' not in peer_config: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'conditionally-advertise', 'exist-map', route_map_in]) + + if 'exist_map' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'conditionally-advertise', 'exist-map', peer_config["exist_map"]]) + if 'non_exist_map' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'conditionally-advertise', 'non-exist-map', peer_config["non_exist_map"]]) + # commit changes self.cli_commit() @@ -421,6 +461,20 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'addpath_per_as' in config: self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'addpath-tx-per-as']) + # Conditional advertisement + if 'advertise_map' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'conditionally-advertise', 'advertise-map', config["advertise_map"]]) + # Either exist-map or non-exist-map needs to be specified + if 'exist_map' not in config and 'non_exist_map' not in config: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'conditionally-advertise', 'exist-map', route_map_in]) + + if 'exist_map' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'conditionally-advertise', 'exist-map', config["exist_map"]]) + if 'non_exist_map' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'address-family', 'ipv4-unicast', 'conditionally-advertise', 'non-exist-map', config["non_exist_map"]]) + for peer, peer_config in neighbor_config.items(): if 'peer_group' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'peer-group', peer_config['peer_group']]) diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 03fb17ba7..d8704727c 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -183,6 +183,28 @@ def verify(bgp): raise ConfigError(f'Neighbor "{peer}" cannot have both ipv6-unicast and ipv6-labeled-unicast configured at the same time!') afi_config = peer_config['address_family'][afi] + + if 'conditionally_advertise' in afi_config: + if 'advertise_map' not in afi_config['conditionally_advertise']: + raise ConfigError('Must speficy advertise-map when conditionally-advertise is in use!') + # Verify advertise-map (which is a route-map) exists + verify_route_map(afi_config['conditionally_advertise']['advertise_map'], bgp) + + if ('exist_map' not in afi_config['conditionally_advertise'] and + 'non_exist_map' not in afi_config['conditionally_advertise']): + raise ConfigError('Must either speficy exist-map or non-exist-map when ' \ + 'conditionally-advertise is in use!') + + if {'exist_map', 'non_exist_map'} <= set(afi_config['conditionally_advertise']): + raise ConfigError('Can not specify both exist-map and non-exist-map for ' \ + 'conditionally-advertise!') + + if 'exist_map' in afi_config['conditionally_advertise']: + verify_route_map(afi_config['conditionally_advertise']['exist_map'], bgp) + + if 'non_exist_map' in afi_config['conditionally_advertise']: + verify_route_map(afi_config['conditionally_advertise']['non_exist_map'], bgp) + # Validate if configured Prefix list exists if 'prefix_list' in afi_config: for tmp in ['import', 'export']: -- cgit v1.2.3 From c3471fe9d4cf0aab46feae94618925a95bcd5411 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 12 Dec 2021 22:34:05 +0100 Subject: validator: T4036: validate if multicast address is single (no netmask) --- src/validators/ipv4-multicast | 2 +- src/validators/ipv6-multicast | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validators/ipv4-multicast b/src/validators/ipv4-multicast index e5cbc9532..5465c728d 100755 --- a/src/validators/ipv4-multicast +++ b/src/validators/ipv4-multicast @@ -1,3 +1,3 @@ #!/bin/sh -ipaddrcheck --is-ipv4-multicast $1 +ipaddrcheck --is-ipv4-multicast $1 && ipaddrcheck --is-ipv4-single $1 diff --git a/src/validators/ipv6-multicast b/src/validators/ipv6-multicast index 66cd90c9c..5afc437e5 100755 --- a/src/validators/ipv6-multicast +++ b/src/validators/ipv6-multicast @@ -1,3 +1,3 @@ #!/bin/sh -ipaddrcheck --is-ipv6-multicast $1 +ipaddrcheck --is-ipv6-multicast $1 && ipaddrcheck --is-ipv6-single $1 -- cgit v1.2.3 From 30311db5a00c78872c9ad9b29e7081e0d81a5362 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 12 Dec 2021 17:30:56 -0600 Subject: graphql: T3993: distinguish queries and mutations; update README.graphql --- src/services/api/graphql/README.graphql | 55 +++++++++++-- src/services/api/graphql/bindings.py | 18 ++++- src/services/api/graphql/graphql/directives.py | 16 ++++ src/services/api/graphql/graphql/mutations.py | 52 ++++++------- src/services/api/graphql/graphql/queries.py | 89 ++++++++++++++++++++++ .../api/graphql/graphql/schema/schema.graphql | 11 ++- .../remove_firewall_address_group_members.py | 14 ++++ src/services/api/graphql/recipes/session.py | 15 ++++ 8 files changed, 230 insertions(+), 40 deletions(-) create mode 100644 src/services/api/graphql/graphql/queries.py diff --git a/src/services/api/graphql/README.graphql b/src/services/api/graphql/README.graphql index a3c30b005..6aa834329 100644 --- a/src/services/api/graphql/README.graphql +++ b/src/services/api/graphql/README.graphql @@ -1,7 +1,12 @@ +The following examples are in the form as entered in the GraphQL +'playground', which is found at: + +https://{{ host_address }}/graphql + Example using GraphQL mutations to configure a DHCP server: -This assumes that the http-api is running: +All examples assume that the http-api is running: 'set service https api' @@ -58,8 +63,8 @@ N.B. fileName can be empty (fileName: "") or data can be empty (data: {}) to save to /config/config.boot; to save to an alternative path, specify fileName. -Similarly, using the same 'endpoint' (meaning the form of the request and -resolver; the actual enpoint for all GraphQL requests is +Similarly, using an analogous 'endpoint' (meaning the form of the request +and resolver; the actual enpoint for all GraphQL requests is https://hostname/graphql), one can load an arbitrary config file from a path. @@ -75,7 +80,7 @@ mutation { Op-mode 'show' commands may be requested by path, e.g.: -mutation { +query { Show (data: {path: ["interfaces", "ethernet", "detail"]}) { success errors @@ -88,16 +93,52 @@ mutation { N.B. to see the output the 'data' field 'result' must be present in the request. -The GraphQL playground will be found at: +Mutations to manipulate firewall address groups: -https://{{ host_address }}/graphql +mutation { + CreateFirewallAddressGroup (data: {name: "ADDR-GRP", address: "10.0.0.1"}) { + success + errors + } +} + +mutation { + UpdateFirewallAddressGroupMembers (data: {name: "ADDR-GRP", + address: ["10.0.0.1-10.0.0.8", "192.168.0.1"]}) { + success + errors + } +} -An equivalent curl command to the first example above would be: +mutation { + RemoveFirewallAddressGroupMembers (data: {name: "ADDR-GRP", + address: "192.168.0.1"}) { + success + errors + } +} + +N.B. The schema for the above specify that 'address' be of the form 'list of +strings' (SDL type [String!]! for UpdateFirewallAddressGroupMembers, where +the ! indicates that the input is required; SDL type [String] in +CreateFirewallAddressGroup, since a group may be created without any +addresses). However, notice that a single string may be passed without being +a member of a list, in which case the specification allows for 'input +coercion': + +http://spec.graphql.org/October2021/#sec-Scalars.Input-Coercion + + +Instead of using the GraphQL playground, an equivalent curl command to the +first example above would be: curl -k 'https://192.168.100.168/graphql' -H 'Content-Type: application/json' --data-binary '{"query": "mutation {createInterfaceEthernet (data: {interface: \"eth1\", address: \"192.168.0.1/24\", description: \"BOB\"}) {success errors data {address}}}"}' Note that the 'mutation' term is prefaced by 'query' in the curl command. +Curl equivalents may be read from within the GraphQL playground at the 'copy +curl' button. + What's here: services diff --git a/src/services/api/graphql/bindings.py b/src/services/api/graphql/bindings.py index 1fbe13d0c..84d719fda 100644 --- a/src/services/api/graphql/bindings.py +++ b/src/services/api/graphql/bindings.py @@ -1,4 +1,20 @@ +# Copyright 2021 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + import vyos.defaults +from . graphql.queries import query from . graphql.mutations import mutation from . graphql.directives import directives_dict from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers @@ -8,6 +24,6 @@ def generate_schema(): type_defs = load_schema_from_path(api_schema_dir) - schema = make_executable_schema(type_defs, mutation, snake_case_fallback_resolvers, directives=directives_dict) + schema = make_executable_schema(type_defs, query, mutation, snake_case_fallback_resolvers, directives=directives_dict) return schema diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py index 10bc522db..0a9298f55 100644 --- a/src/services/api/graphql/graphql/directives.py +++ b/src/services/api/graphql/graphql/directives.py @@ -1,4 +1,20 @@ +# Copyright 2021 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + from ariadne import SchemaDirectiveVisitor, ObjectType +from . queries import * from . mutations import * def non(arg): diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index 8e5aab56d..0c3eb702a 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -1,3 +1,17 @@ +# Copyright 2021 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . from importlib import import_module from typing import Any, Dict @@ -10,7 +24,7 @@ from api.graphql.recipes.session import Session mutation = ObjectType("Mutation") -def make_resolver(mutation_name, class_name, session_func): +def make_mutation_resolver(mutation_name, class_name, session_func): """Dynamically generate a resolver for the mutation named in the schema by 'mutation_name'. @@ -66,34 +80,20 @@ def make_resolver(mutation_name, class_name, session_func): return func_impl -def make_configure_resolver(mutation_name): - class_name = mutation_name - return make_resolver(mutation_name, class_name, 'configure') +def make_prefix_resolver(mutation_name, prefix=[]): + for pre in prefix: + Pre = pre.capitalize() + if Pre in mutation_name: + class_name = mutation_name.replace(Pre, '', 1) + return make_mutation_resolver(mutation_name, class_name, pre) + raise Exception -def make_show_config_resolver(mutation_name): +def make_configure_resolver(mutation_name): class_name = mutation_name - return make_resolver(mutation_name, class_name, 'show_config') + return make_mutation_resolver(mutation_name, class_name, 'configure') def make_config_file_resolver(mutation_name): - if 'Save' in mutation_name: - class_name = mutation_name.replace('Save', '', 1) - return make_resolver(mutation_name, class_name, 'save') - elif 'Load' in mutation_name: - class_name = mutation_name.replace('Load', '', 1) - return make_resolver(mutation_name, class_name, 'load') - else: - raise Exception - -def make_show_resolver(mutation_name): - class_name = mutation_name - return make_resolver(mutation_name, class_name, 'show') + return make_prefix_resolver(mutation_name, prefix=['save', 'load']) def make_image_resolver(mutation_name): - if 'Add' in mutation_name: - class_name = mutation_name.replace('Add', '', 1) - return make_resolver(mutation_name, class_name, 'add') - elif 'Delete' in mutation_name: - class_name = mutation_name.replace('Delete', '', 1) - return make_resolver(mutation_name, class_name, 'delete') - else: - raise Exception + return make_prefix_resolver(mutation_name, prefix=['add', 'delete']) diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py new file mode 100644 index 000000000..e1868091e --- /dev/null +++ b/src/services/api/graphql/graphql/queries.py @@ -0,0 +1,89 @@ +# Copyright 2021 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + +from importlib import import_module +from typing import Any, Dict +from ariadne import ObjectType, convert_kwargs_to_snake_case, convert_camel_case_to_snake +from graphql import GraphQLResolveInfo +from makefun import with_signature + +from .. import state +from api.graphql.recipes.session import Session + +query = ObjectType("Query") + +def make_query_resolver(query_name, class_name, session_func): + """Dynamically generate a resolver for the query named in the + schema by 'query_name'. + + Dynamic generation is provided using the package 'makefun' (via the + decorator 'with_signature'), which provides signature-preserving + function wrappers; it provides several improvements over, say, + functools.wraps. + + :raise Exception: + raising ConfigErrors, or internal errors + """ + + func_base_name = convert_camel_case_to_snake(class_name) + resolver_name = f'resolve_{func_base_name}' + func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Dict)' + + @query.field(query_name) + @convert_kwargs_to_snake_case + @with_signature(func_sig, func_name=resolver_name) + async def func_impl(*args, **kwargs): + try: + if 'data' not in kwargs: + return { + "success": False, + "errors": ['missing data'] + } + + data = kwargs['data'] + session = state.settings['app'].state.vyos_session + + # one may override the session functions with a local subclass + try: + mod = import_module(f'api.graphql.recipes.{func_base_name}') + klass = getattr(mod, class_name) + except ImportError: + # otherwise, dynamically generate subclass to invoke subclass + # name based templates + klass = type(class_name, (Session,), {}) + k = klass(session, data) + method = getattr(k, session_func) + result = method() + data['result'] = result + + return { + "success": True, + "data": data + } + except Exception as error: + return { + "success": False, + "errors": [str(error)] + } + + return func_impl + +def make_show_config_resolver(query_name): + class_name = query_name + return make_query_resolver(query_name, class_name, 'show_config') + +def make_show_resolver(query_name): + class_name = query_name + return make_query_resolver(query_name, class_name, 'show') diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql index c6899bee6..ce58b991a 100644 --- a/src/services/api/graphql/graphql/schema/schema.graphql +++ b/src/services/api/graphql/graphql/schema/schema.graphql @@ -3,16 +3,17 @@ schema { mutation: Mutation } -type Query { - _dummy: String -} - directive @configure on FIELD_DEFINITION directive @configfile on FIELD_DEFINITION directive @show on FIELD_DEFINITION directive @showconfig on FIELD_DEFINITION directive @image on FIELD_DEFINITION +type Query { + Show(data: ShowInput) : ShowResult @show + ShowConfig(data: ShowConfigInput) : ShowConfigResult @showconfig +} + type Mutation { CreateDhcpServer(data: DhcpServerConfigInput) : CreateDhcpServerResult @configure CreateInterfaceEthernet(data: InterfaceEthernetConfigInput) : CreateInterfaceEthernetResult @configure @@ -21,8 +22,6 @@ type Mutation { RemoveFirewallAddressGroupMembers(data: RemoveFirewallAddressGroupMembersInput) : RemoveFirewallAddressGroupMembersResult @configure SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configfile LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configfile - Show(data: ShowInput) : ShowResult @show - ShowConfig(data: ShowConfigInput) : ShowConfigResult @showconfig AddSystemImage(data: AddSystemImageInput) : AddSystemImageResult @image DeleteSystemImage(data: DeleteSystemImageInput) : DeleteSystemImageResult @image } diff --git a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py b/src/services/api/graphql/recipes/remove_firewall_address_group_members.py index cde30c27a..b91932e14 100644 --- a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py +++ b/src/services/api/graphql/recipes/remove_firewall_address_group_members.py @@ -1,3 +1,17 @@ +# Copyright 2021 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . from . session import Session diff --git a/src/services/api/graphql/recipes/session.py b/src/services/api/graphql/recipes/session.py index 5ece78ee6..1f844ff70 100644 --- a/src/services/api/graphql/recipes/session.py +++ b/src/services/api/graphql/recipes/session.py @@ -1,3 +1,18 @@ +# Copyright 2021 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see . + import json from ariadne import convert_camel_case_to_snake -- cgit v1.2.3 From 92c4cc5e1248b3c7ffda03e23eeb21e2073ba7f0 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 12 Dec 2021 17:35:42 -0600 Subject: graphql: T3993: add requests for firewall ipv6-address-group --- src/services/api/graphql/README.graphql | 6 +++ .../graphql/graphql/schema/firewall_group.graphql | 48 ++++++++++++++++++++++ .../api/graphql/graphql/schema/schema.graphql | 3 ++ .../create_firewall_address_ipv_6_group.tmpl | 4 ++ ...emove_firewall_address_ipv_6_group_members.tmpl | 3 ++ ...pdate_firewall_address_ipv_6_group_members.tmpl | 3 ++ 6 files changed, 67 insertions(+) create mode 100644 src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl create mode 100644 src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl create mode 100644 src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl diff --git a/src/services/api/graphql/README.graphql b/src/services/api/graphql/README.graphql index 6aa834329..1133d79ed 100644 --- a/src/services/api/graphql/README.graphql +++ b/src/services/api/graphql/README.graphql @@ -128,6 +128,12 @@ coercion': http://spec.graphql.org/October2021/#sec-Scalars.Input-Coercion +Similarly, IPv6 versions of the above: + +CreateFirewallAddressIpv6Group +UpdateFirewallAddressIpv6GroupMembers +RemoveFirewallAddressIpv6GroupMembers + Instead of using the GraphQL playground, an equivalent curl command to the first example above would be: diff --git a/src/services/api/graphql/graphql/schema/firewall_group.graphql b/src/services/api/graphql/graphql/schema/firewall_group.graphql index efe7de632..d89904b9e 100644 --- a/src/services/api/graphql/graphql/schema/firewall_group.graphql +++ b/src/services/api/graphql/graphql/schema/firewall_group.graphql @@ -45,3 +45,51 @@ type RemoveFirewallAddressGroupMembersResult { success: Boolean! errors: [String] } + +input CreateFirewallAddressIpv6GroupInput { + name: String! + address: [String] +} + +type CreateFirewallAddressIpv6Group { + name: String! + address: [String] +} + +type CreateFirewallAddressIpv6GroupResult { + data: CreateFirewallAddressIpv6Group + success: Boolean! + errors: [String] +} + +input UpdateFirewallAddressIpv6GroupMembersInput { + name: String! + address: [String!]! +} + +type UpdateFirewallAddressIpv6GroupMembers { + name: String! + address: [String!]! +} + +type UpdateFirewallAddressIpv6GroupMembersResult { + data: UpdateFirewallAddressIpv6GroupMembers + success: Boolean! + errors: [String] +} + +input RemoveFirewallAddressIpv6GroupMembersInput { + name: String! + address: [String!]! +} + +type RemoveFirewallAddressIpv6GroupMembers { + name: String! + address: [String!]! +} + +type RemoveFirewallAddressIpv6GroupMembersResult { + data: RemoveFirewallAddressIpv6GroupMembers + success: Boolean! + errors: [String] +} diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql index ce58b991a..952e46f34 100644 --- a/src/services/api/graphql/graphql/schema/schema.graphql +++ b/src/services/api/graphql/graphql/schema/schema.graphql @@ -20,6 +20,9 @@ type Mutation { CreateFirewallAddressGroup(data: CreateFirewallAddressGroupInput) : CreateFirewallAddressGroupResult @configure UpdateFirewallAddressGroupMembers(data: UpdateFirewallAddressGroupMembersInput) : UpdateFirewallAddressGroupMembersResult @configure RemoveFirewallAddressGroupMembers(data: RemoveFirewallAddressGroupMembersInput) : RemoveFirewallAddressGroupMembersResult @configure + CreateFirewallAddressIpv6Group(data: CreateFirewallAddressIpv6GroupInput) : CreateFirewallAddressIpv6GroupResult @configure + UpdateFirewallAddressIpv6GroupMembers(data: UpdateFirewallAddressIpv6GroupMembersInput) : UpdateFirewallAddressIpv6GroupMembersResult @configure + RemoveFirewallAddressIpv6GroupMembers(data: RemoveFirewallAddressIpv6GroupMembersInput) : RemoveFirewallAddressIpv6GroupMembersResult @configure SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configfile LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configfile AddSystemImage(data: AddSystemImageInput) : AddSystemImageResult @image diff --git a/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl b/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl new file mode 100644 index 000000000..e9b660722 --- /dev/null +++ b/src/services/api/graphql/recipes/templates/create_firewall_address_ipv_6_group.tmpl @@ -0,0 +1,4 @@ +set firewall group ipv6-address-group {{ name }} +{% for add in address %} +set firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl new file mode 100644 index 000000000..0efa0b226 --- /dev/null +++ b/src/services/api/graphql/recipes/templates/remove_firewall_address_ipv_6_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +delete firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl b/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl new file mode 100644 index 000000000..f98a5517c --- /dev/null +++ b/src/services/api/graphql/recipes/templates/update_firewall_address_ipv_6_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +set firewall group ipv6-address-group {{ name }} address {{ add }} +{% endfor %} -- cgit v1.2.3 From 52e6ea6119a351757ce959286e24fd7607b5e1a5 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 13 Dec 2021 09:50:38 -0600 Subject: configd: T2582: add smoketest for vyos-configd initialization --- smoketest/scripts/cli/test_configd_init.py | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100755 smoketest/scripts/cli/test_configd_init.py diff --git a/smoketest/scripts/cli/test_configd_init.py b/smoketest/scripts/cli/test_configd_init.py new file mode 100755 index 000000000..5dec89963 --- /dev/null +++ b/smoketest/scripts/cli/test_configd_init.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 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 unittest +from time import sleep + +from vyos.util import cmd, is_systemd_service_running + +class TestConfigdInit(unittest.TestCase): + def setUp(self): + self.running_state = is_systemd_service_running('vyos-configd.service') + + def test_configd_init(self): + if not self.running_state: + cmd('sudo systemctl start vyos-configd.service') + # allow time for init to succeed/fail + sleep(2) + self.assertTrue(is_systemd_service_running('vyos-configd.service')) + + def tearDown(self): + if not self.running_state: + cmd('sudo systemctl stop vyos-configd.service') + +if __name__ == '__main__': + unittest.main(verbosity=2) -- cgit v1.2.3 From 0e3c35e6517f5cfebb4206c735a2ea976a7fd383 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 10 Dec 2021 14:41:23 -0600 Subject: http-api: T4071: allow API to bind to unix domain socket --- data/templates/https/nginx.default.tmpl | 4 ++++ interface-definitions/https.xml.in | 6 ++++++ python/vyos/defaults.py | 5 +++-- src/conf_mode/http-api.py | 11 +++++++---- src/conf_mode/https.py | 2 ++ src/services/vyos-http-api-server | 14 +++++++++----- 6 files changed, 31 insertions(+), 11 deletions(-) diff --git a/data/templates/https/nginx.default.tmpl b/data/templates/https/nginx.default.tmpl index 9d73baeee..ac9203e83 100644 --- a/data/templates/https/nginx.default.tmpl +++ b/data/templates/https/nginx.default.tmpl @@ -44,7 +44,11 @@ server { # proxy settings for HTTP API, if enabled; 503, if not location ~ /(retrieve|configure|config-file|image|generate|show|docs|openapi.json|redoc|graphql) { {% if server.api %} +{% if server.api.socket %} + proxy_pass http://unix:/run/api.sock; +{% else %} proxy_pass http://localhost:{{ server.api.port }}; +{% endif %} proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 600; diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in index d26cd5e7a..33e43a432 100644 --- a/interface-definitions/https.xml.in +++ b/interface-definitions/https.xml.in @@ -101,6 +101,12 @@ + + + Run server on Unix domain socket + + + diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index f355c4919..c77b695bd 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -46,8 +46,9 @@ https_data = { api_data = { 'listen_address' : '127.0.0.1', 'port' : '8080', - 'strict' : 'false', - 'debug' : 'false', + 'socket' : False, + 'strict' : False, + 'debug' : False, 'api_keys' : [ {"id": "testapp", "key": "qwerty"} ] } diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py index 4bfcbeb47..cd0191599 100755 --- a/src/conf_mode/http-api.py +++ b/src/conf_mode/http-api.py @@ -31,7 +31,7 @@ from vyos.util import call from vyos import airbag airbag.enable() -config_file = '/etc/vyos/http-api.conf' +api_conf_file = '/etc/vyos/http-api.conf' vyos_conf_scripts_dir=vyos.defaults.directories['conf_mode'] @@ -55,10 +55,13 @@ def get_config(config=None): conf.set_level('service https api') if conf.exists('strict'): - http_api['strict'] = 'true' + http_api['strict'] = True if conf.exists('debug'): - http_api['debug'] = 'true' + http_api['debug'] = True + + if conf.exists('socket'): + http_api['socket'] = True if conf.exists('port'): port = conf.return_value('port') @@ -88,7 +91,7 @@ def generate(http_api): if not os.path.exists('/etc/vyos'): os.mkdir('/etc/vyos') - with open(config_file, 'w') as f: + with open(api_conf_file, 'w') as f: json.dump(http_api, f, indent=2) return None diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index cd5073aa2..053ee5d4a 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -191,6 +191,8 @@ def generate(https): vhosts = https.get('api-restrict', {}).get('virtual-host', []) if vhosts: api_data['vhost'] = vhosts[:] + if 'socket' in list(api_settings): + api_data['socket'] = True if api_data: vhost_list = api_data.get('vhost', []) diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index aa7ac6708..f79058683 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -640,15 +640,19 @@ if __name__ == '__main__': app.state.vyos_session = config_session app.state.vyos_keys = server_config['api_keys'] - app.state.vyos_debug = bool(server_config['debug'] == 'true') - app.state.vyos_strict = bool(server_config['strict'] == 'true') + app.state.vyos_debug = server_config['debug'] + app.state.vyos_strict = server_config['strict'] api.graphql.state.settings['app'] = app try: - uvicorn.run(app, host=server_config["listen_address"], - port=int(server_config["port"]), - proxy_headers=True) + if not server_config['socket']: + uvicorn.run(app, host=server_config["listen_address"], + port=int(server_config["port"]), + proxy_headers=True) + else: + uvicorn.run(app, uds="/run/api.sock", + proxy_headers=True) except OSError as err: logger.critical(f"OSError {err}") sys.exit(1) -- cgit v1.2.3 From 3f2173873e0519ea253759e7e119b2bcaea596bb Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 13 Dec 2021 21:06:47 +0100 Subject: op-mode: xml: refactor "show ip(v6) route" to use #include building blocks This change then adds supper to filter for given route types under a VRF instance. You will have "show ip route bgp" in the global VRF and "show ip route vrf red bgp" in the red VRF. Same commands apply to IPv6 routes. --- op-mode-definitions/include/show-route-bgp.xml.i | 8 ++ .../include/show-route-connected.xml.i | 8 ++ op-mode-definitions/include/show-route-isis.xml.i | 8 ++ .../include/show-route-kernel.xml.i | 8 ++ op-mode-definitions/include/show-route-ospf.xml.i | 8 ++ .../include/show-route-ospfv3.xml.i | 8 ++ op-mode-definitions/include/show-route-rip.xml.i | 8 ++ op-mode-definitions/include/show-route-ripng.xml.i | 8 ++ .../include/show-route-static.xml.i | 8 ++ .../include/show-route-summary.xml.i | 8 ++ .../include/show-route-supernets-only.xml.i | 8 ++ op-mode-definitions/include/show-route-table.xml.i | 17 ++++ op-mode-definitions/include/show-route-tag.xml.i | 16 +++ op-mode-definitions/show-ip-route.xml.in | 109 +++++---------------- op-mode-definitions/show-ipv6-route.xml.in | 97 +++++------------- 15 files changed, 172 insertions(+), 155 deletions(-) create mode 100644 op-mode-definitions/include/show-route-bgp.xml.i create mode 100644 op-mode-definitions/include/show-route-connected.xml.i create mode 100644 op-mode-definitions/include/show-route-isis.xml.i create mode 100644 op-mode-definitions/include/show-route-kernel.xml.i create mode 100644 op-mode-definitions/include/show-route-ospf.xml.i create mode 100644 op-mode-definitions/include/show-route-ospfv3.xml.i create mode 100644 op-mode-definitions/include/show-route-rip.xml.i create mode 100644 op-mode-definitions/include/show-route-ripng.xml.i create mode 100644 op-mode-definitions/include/show-route-static.xml.i create mode 100644 op-mode-definitions/include/show-route-summary.xml.i create mode 100644 op-mode-definitions/include/show-route-supernets-only.xml.i create mode 100644 op-mode-definitions/include/show-route-table.xml.i create mode 100644 op-mode-definitions/include/show-route-tag.xml.i diff --git a/op-mode-definitions/include/show-route-bgp.xml.i b/op-mode-definitions/include/show-route-bgp.xml.i new file mode 100644 index 000000000..5c26bf43f --- /dev/null +++ b/op-mode-definitions/include/show-route-bgp.xml.i @@ -0,0 +1,8 @@ + + + + Border Gateway Protocol (BGP) + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/show-route-connected.xml.i b/op-mode-definitions/include/show-route-connected.xml.i new file mode 100644 index 000000000..37364de64 --- /dev/null +++ b/op-mode-definitions/include/show-route-connected.xml.i @@ -0,0 +1,8 @@ + + + + Connected routes (directly attached subnet or host) + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/show-route-isis.xml.i b/op-mode-definitions/include/show-route-isis.xml.i new file mode 100644 index 000000000..9ff2ccdc5 --- /dev/null +++ b/op-mode-definitions/include/show-route-isis.xml.i @@ -0,0 +1,8 @@ + + + + Intermediate System to Intermediate System (IS-IS) + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/show-route-kernel.xml.i b/op-mode-definitions/include/show-route-kernel.xml.i new file mode 100644 index 000000000..8c5ac414e --- /dev/null +++ b/op-mode-definitions/include/show-route-kernel.xml.i @@ -0,0 +1,8 @@ + + + + Kernel routes (not installed via the zebra RIB) + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/show-route-ospf.xml.i b/op-mode-definitions/include/show-route-ospf.xml.i new file mode 100644 index 000000000..1122aaba5 --- /dev/null +++ b/op-mode-definitions/include/show-route-ospf.xml.i @@ -0,0 +1,8 @@ + + + + Open Shortest Path First (OSPFv2) + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/show-route-ospfv3.xml.i b/op-mode-definitions/include/show-route-ospfv3.xml.i new file mode 100644 index 000000000..c7a11b7ba --- /dev/null +++ b/op-mode-definitions/include/show-route-ospfv3.xml.i @@ -0,0 +1,8 @@ + + + + Open Shortest Path First (IPv6) (OSPFv3) + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/show-route-rip.xml.i b/op-mode-definitions/include/show-route-rip.xml.i new file mode 100644 index 000000000..3c2fede28 --- /dev/null +++ b/op-mode-definitions/include/show-route-rip.xml.i @@ -0,0 +1,8 @@ + + + + Routing Information Protocol (RIP) + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/show-route-ripng.xml.i b/op-mode-definitions/include/show-route-ripng.xml.i new file mode 100644 index 000000000..6e59cb054 --- /dev/null +++ b/op-mode-definitions/include/show-route-ripng.xml.i @@ -0,0 +1,8 @@ + + + + Routing Information Protocol next-generation (IPv6) (RIPng) + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/show-route-static.xml.i b/op-mode-definitions/include/show-route-static.xml.i new file mode 100644 index 000000000..c2e396763 --- /dev/null +++ b/op-mode-definitions/include/show-route-static.xml.i @@ -0,0 +1,8 @@ + + + + Statically configured routes + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/show-route-summary.xml.i b/op-mode-definitions/include/show-route-summary.xml.i new file mode 100644 index 000000000..471124562 --- /dev/null +++ b/op-mode-definitions/include/show-route-summary.xml.i @@ -0,0 +1,8 @@ + + + + Summary of all routes + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/show-route-supernets-only.xml.i b/op-mode-definitions/include/show-route-supernets-only.xml.i new file mode 100644 index 000000000..4d1e7c51f --- /dev/null +++ b/op-mode-definitions/include/show-route-supernets-only.xml.i @@ -0,0 +1,8 @@ + + + + Show supernet entries only + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/show-route-table.xml.i b/op-mode-definitions/include/show-route-table.xml.i new file mode 100644 index 000000000..c3cf82a86 --- /dev/null +++ b/op-mode-definitions/include/show-route-table.xml.i @@ -0,0 +1,17 @@ + + + + Table to display + + + + + The table number to display + + all + protocols static table + + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/include/show-route-tag.xml.i b/op-mode-definitions/include/show-route-tag.xml.i new file mode 100644 index 000000000..8bfa0ae4e --- /dev/null +++ b/op-mode-definitions/include/show-route-tag.xml.i @@ -0,0 +1,16 @@ + + + + Show only routes with tag + + + + + Tag value + + <1-4294967295> + + + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + diff --git a/op-mode-definitions/show-ip-route.xml.in b/op-mode-definitions/show-ip-route.xml.in index fdbb6859d..1e906672d 100644 --- a/op-mode-definitions/show-ip-route.xml.in +++ b/op-mode-definitions/show-ip-route.xml.in @@ -13,12 +13,7 @@ vtysh -c "show ip route" - - - Show IP BGP routes - - vtysh -c "show ip route bgp" - + #include Show kernel route cache @@ -34,12 +29,7 @@ ip -s route list cache $5 - - - Show IP connected routes - - vtysh -c "show ip route connected" - + #include Show kernel route table @@ -55,76 +45,15 @@ ip -s route list $5 - - - Show IP IS-IS routes - - vtysh -c "show ip route isis" - - - - Show IP kernel routes - - vtysh -c "show ip route kernel" - - - - Show IP OSPF routes - - vtysh -c "show ip route ospf" - - - - Show IP RIP routes - - vtysh -c "show ip route rip" - - - - Show IP static routes - - vtysh -c "show ip route static" - - - - Show IP routes summary - - vtysh -c "show ip route summary" - - - - Show IP supernet routes - - vtysh -c "show ip route supernets-only" - - - - Show IP routes in policy table - - - - - Show IP routes in policy table - - <1-200> - - - vtysh -c "show ip route table $5" - - - - Show only routes with tag - - - - - Tag value - - <1-4294967295> - - - vtysh -c "show ip route tag $5" - + #include + #include + #include + #include + #include + #include + #include + #include + #include Show IP routes in VRF @@ -133,7 +62,19 @@ vrf name - vtysh -c "show ip route vrf $5" + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + @@ -144,7 +85,7 @@ <x.x.x.x> <x.x.x.x/x> - vtysh -c "show ip route $4" + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ diff --git a/op-mode-definitions/show-ipv6-route.xml.in b/op-mode-definitions/show-ipv6-route.xml.in index 8624574ac..2c5024991 100644 --- a/op-mode-definitions/show-ipv6-route.xml.in +++ b/op-mode-definitions/show-ipv6-route.xml.in @@ -13,12 +13,7 @@ vtysh -c "show ipv6 route" - - - Show IPv6 BGP routes - - vtysh -c "show ipv6 route bgp" - + #include Show kernel IPv6 route cache @@ -34,12 +29,7 @@ ip -s -f inet6 route list cache $5 - - - Show IPv6 connected routes - - vtysh -c "show ipv6 route connected" - + #include Show kernel IPv6 route table @@ -55,71 +45,36 @@ ip -s -f inet6 route list $5 - - - Show IPv6 IS-IS routes - - vtysh -c "show ipv6 route isis" - - - - Show IPv6 Kernel routes - - vtysh -c "show ipv6 route kernel" - - - - Show IPv6 OSPF routes - - vtysh -c "show ipv6 route ospf6" - - - - Show IPv6 RIPNG routes - - vtysh -c "show ipv6 route ripng" - - - - Show IPv6 static routes - - vtysh -c "show ipv6 route static" - - - - Show IPv6 routes summary - - vtysh -c "show ipv6 route summary" - - - - Show IPv6 routes in policy tables - - vtysh -c "show ipv6 route table all" - - - - Show IPv6 routes in specific policy table - - protocols static table - - - vtysh -c "show ipv6 route table $5" - - - - Show IPv6 routes in VRFs - - vtysh -c "show ipv6 route vrf all" - + #include + #include + #include + #include + #include + #include + #include + #include - Show IPv6 routes in specific VRF + Show IPv6 routes in VRF + all vrf name - vtysh -c "show ipv6 route vrf $5" + ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + -- cgit v1.2.3 From a6f951ed7f4669b7601f5f7e6c37451ed855d40b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 13 Dec 2021 21:40:19 +0100 Subject: op-mode: BGP wide option is not availbale for L2VPN/EVPN AFI --- op-mode-definitions/include/bgp/afi-common.xml.i | 1 - op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/op-mode-definitions/include/bgp/afi-common.xml.i b/op-mode-definitions/include/bgp/afi-common.xml.i index 4d5f56656..acf20d950 100644 --- a/op-mode-definitions/include/bgp/afi-common.xml.i +++ b/op-mode-definitions/include/bgp/afi-common.xml.i @@ -61,5 +61,4 @@ -#include diff --git a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i index a51595b7f..084f5da83 100644 --- a/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i +++ b/op-mode-definitions/include/bgp/afi-ipv4-ipv6-common.xml.i @@ -230,4 +230,5 @@ ${vyos_op_scripts_dir}/vtysh_wrapper.sh $@ +#include -- cgit v1.2.3 From 6f9ba4e577a6470372a478310ea0c49a6400b8e6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 15 Dec 2021 22:15:58 +0100 Subject: op-mode: bfd: T4073: "show protocols bfd peer " returned invalid session data Due to the AWK regex pattern data from a different peer was returned as the first match was shown. --- op-mode-definitions/show-bfd.xml.in | 50 ++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/op-mode-definitions/show-bfd.xml.in b/op-mode-definitions/show-bfd.xml.in index 7339c92a2..39e42e6ec 100644 --- a/op-mode-definitions/show-bfd.xml.in +++ b/op-mode-definitions/show-bfd.xml.in @@ -2,7 +2,55 @@ - #include + + + Show Bidirectional Forwarding Detection (BFD) + + + + + Show all Bidirectional Forwarding Detection (BFD) peer status + + + + + Show Bidirectional Forwarding Detection (BFD) peer status + + + + + vtysh -c "show bfd peers" | sed -n "/peer $4 /,/^$/p" + + + + Show Bidirectional Forwarding Detection (BFD) peer counters + + vtysh -c "show bfd peers counters" | sed -n "/peer $4 /,/^$/p" + + + + + + Show Bidirectional Forwarding Detection peers + + vtysh -c "show bfd peers" + + + + Show Bidirectional Forwarding Detection (BFD) peer counters + + vtysh -c "show bfd peers counters" + + + + Show Bidirectional Forwarding Detection (BFD) peers brief + + vtysh -c "show bfd peers brief" + + + + + -- cgit v1.2.3 From 25d95b1f73f3e5e0fd2dfc9199f46d808c68af43 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 15 Dec 2021 22:18:34 +0100 Subject: op-mode: T4073: drop "show protocols bfd" in favour of "show bfd" --- op-mode-definitions/include/bfd-common.xml.i | 54 ---------------------------- op-mode-definitions/show-protocols.xml.in | 1 - 2 files changed, 55 deletions(-) delete mode 100644 op-mode-definitions/include/bfd-common.xml.i diff --git a/op-mode-definitions/include/bfd-common.xml.i b/op-mode-definitions/include/bfd-common.xml.i deleted file mode 100644 index eebfbdad4..000000000 --- a/op-mode-definitions/include/bfd-common.xml.i +++ /dev/null @@ -1,54 +0,0 @@ - - - - Show Bidirectional Forwarding Detection (BFD) - - - - - Show all Bidirectional Forwarding Detection (BFD) peer status - - vtysh -c "show bfd peers" - - - - Show Bidirectional Forwarding Detection (BFD) peer counters - - vtysh -c "show bfd peers counters" - - - - - - Show Bidirectional Forwarding Detection (BFD) peer status - - - - - vtysh -c "show bfd peers" | awk -v BFD_PEER=$5 'BEGIN { regex = sprintf("(peer %s.*)vrf", BFD_PEER) } { if (match($0, regex, bfd_peer_value)) peer=bfd_peer_value[1] } END { if (peer) system("vtysh -c \"show bfd " peer "\"") }' - - - - Show Bidirectional Forwarding Detection (BFD) peer counters - - vtysh -c "show bfd peers" | awk -v BFD_PEER=$5 'BEGIN { regex = sprintf("(peer %s.*)vrf", BFD_PEER) } { if (match($0, regex, bfd_peer_value)) peer=bfd_peer_value[1] } END { if (peer) system("vtysh -c \"show bfd " peer " counters\"") }' - - - - - - Show Bidirectional Forwarding Detection peers - - vtysh -c "show bfd peers" - - - - Show Bidirectional Forwarding Detection (BFD) peers brief - - vtysh -c "show bfd peers brief" - - - - - - diff --git a/op-mode-definitions/show-protocols.xml.in b/op-mode-definitions/show-protocols.xml.in index 48bfa10dc..698001b76 100644 --- a/op-mode-definitions/show-protocols.xml.in +++ b/op-mode-definitions/show-protocols.xml.in @@ -7,7 +7,6 @@ Show protocol specific information - #include Show static protocol parameters -- cgit v1.2.3 From 55f8ede2d09a9ad095f9ec5c2a729f8c5fb6aafa Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 15 Dec 2021 14:45:25 -0600 Subject: http-api: T4076: allow setting CORS option 'Access-Control-Allow-Origin' --- interface-definitions/https.xml.in | 13 +++++++++++++ src/conf_mode/http-api.py | 6 ++++++ src/services/vyos-http-api-server | 18 +++++++++++++----- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in index 33e43a432..6fea2f1f6 100644 --- a/interface-definitions/https.xml.in +++ b/interface-definitions/https.xml.in @@ -107,6 +107,19 @@ + + + Set CORS options + + + + + Allow resource request from origin + + + + + diff --git a/src/conf_mode/http-api.py b/src/conf_mode/http-api.py index cd0191599..ea0743cd5 100755 --- a/src/conf_mode/http-api.py +++ b/src/conf_mode/http-api.py @@ -67,6 +67,12 @@ def get_config(config=None): port = conf.return_value('port') http_api['port'] = port + if conf.exists('cors'): + http_api['cors'] = {} + if conf.exists('cors allow-origin'): + origins = conf.return_values('cors allow-origin') + http_api['cors']['origins'] = origins[:] + if conf.exists('keys'): for name in conf.list_nodes('keys id'): if conf.exists('keys id {0} key'.format(name)): diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index f79058683..06871f1d6 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -32,6 +32,7 @@ from fastapi.responses import HTMLResponse from fastapi.exceptions import RequestValidationError from fastapi.routing import APIRoute from pydantic import BaseModel, StrictStr, validator +from starlette.middleware.cors import CORSMiddleware from starlette.datastructures import FormData from starlette.formparsers import FormParser, MultiPartParser from multipart.multipart import parse_options_header @@ -610,13 +611,19 @@ def show_op(data: ShowModel): # GraphQL integration ### -from api.graphql.bindings import generate_schema +def graphql_init(fast_api_app): + from api.graphql.bindings import generate_schema -api.graphql.state.init() + api.graphql.state.init() + api.graphql.state.settings['app'] = app -schema = generate_schema() + schema = generate_schema() -app.add_route('/graphql', GraphQL(schema, debug=True)) + if app.state.vyos_origins: + origins = app.state.vyos_origins + app.add_route('/graphql', CORSMiddleware(GraphQL(schema, debug=True), allow_origins=origins, allow_methods=("GET", "POST", "OPTIONS"))) + else: + app.add_route('/graphql', GraphQL(schema, debug=True)) ### @@ -642,8 +649,9 @@ if __name__ == '__main__': app.state.vyos_debug = server_config['debug'] app.state.vyos_strict = server_config['strict'] + app.state.vyos_origins = server_config.get('cors', {}).get('origins', []) - api.graphql.state.settings['app'] = app + graphql_init(app) try: if not server_config['socket']: -- cgit v1.2.3