diff --git a/data/templates/lldp/lldpd.j2 b/data/templates/lldp/lldpd.j2 index 3c499197d..6ae063c4b 100644 --- a/data/templates/lldp/lldpd.j2 +++ b/data/templates/lldp/lldpd.j2 @@ -1,2 +1,2 @@ ### Autogenerated by lldp.py ### -DAEMON_ARGS="-M 4 {{ '-x' if snmp.enable is vyos_defined }} {{ '-c' if legacy_protocols.cdp is vyos_defined }} {{ '-e' if legacy_protocols.edp is vyos_defined }} {{ '-f' if legacy_protocols.fdp is vyos_defined }} {{ '-s' if legacy_protocols.sonmp is vyos_defined }}" +DAEMON_ARGS="-M 4 {{ '-x' if snmp is vyos_defined }} {{ '-c' if legacy_protocols.cdp is vyos_defined }} {{ '-e' if legacy_protocols.edp is vyos_defined }} {{ '-f' if legacy_protocols.fdp is vyos_defined }} {{ '-s' if legacy_protocols.sonmp is vyos_defined }}" diff --git a/interface-definitions/include/version/lldp-version.xml.i b/interface-definitions/include/version/lldp-version.xml.i index 0deb73279..b41d80451 100644 --- a/interface-definitions/include/version/lldp-version.xml.i +++ b/interface-definitions/include/version/lldp-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/lldp-version.xml.i --> -<syntaxVersion component='lldp' version='1'></syntaxVersion> +<syntaxVersion component='lldp' version='2'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/lldp.xml.in b/interface-definitions/lldp.xml.in index 738bb11c1..25fb575b6 100644 --- a/interface-definitions/lldp.xml.in +++ b/interface-definitions/lldp.xml.in @@ -1,195 +1,188 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="service"> <children> <node name="lldp" owner="${vyos_conf_scripts_dir}/lldp.py"> <properties> <help>LLDP settings</help> <priority>985</priority> </properties> <children> <tagNode name="interface"> <properties> <help>Location data for interface</help> <valueHelp> <format>all</format> <description>Location data all interfaces</description> </valueHelp> <valueHelp> <format>txt</format> <description>Location data for a specific interface</description> </valueHelp> <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> <list>all</list> </completionHelp> </properties> <children> #include <include/generic-disable-node.xml.i> <node name="location"> <properties> <help>LLDP-MED location data</help> </properties> <children> <node name="coordinate-based"> <properties> <help>Coordinate based location</help> </properties> <children> <leafNode name="altitude"> <properties> <help>Altitude in meters</help> <valueHelp> <format>0</format> <description>No altitude</description> </valueHelp> <valueHelp> <format>[+-]<meters></format> <description>Altitude in meters</description> </valueHelp> <constraintErrorMessage>Altitude should be a positive or negative number</constraintErrorMessage> <constraint> <validator name="numeric"/> </constraint> </properties> <defaultValue>0</defaultValue> </leafNode> <leafNode name="datum"> <properties> <help>Coordinate datum type</help> <valueHelp> <format>WGS84</format> <description>WGS84</description> </valueHelp> <valueHelp> <format>NAD83</format> <description>NAD83</description> </valueHelp> <valueHelp> <format>MLLW</format> <description>NAD83/MLLW</description> </valueHelp> <completionHelp> <list>WGS84 NAD83 MLLW</list> </completionHelp> <constraintErrorMessage>Datum should be WGS84, NAD83, or MLLW</constraintErrorMessage> <constraint> <regex>(WGS84|NAD83|MLLW)</regex> </constraint> </properties> <defaultValue>WGS84</defaultValue> </leafNode> <leafNode name="latitude"> <properties> <help>Latitude</help> <valueHelp> <format><latitude></format> <description>Latitude (example "37.524449N")</description> </valueHelp> <constraintErrorMessage>Latitude should be a number followed by S or N</constraintErrorMessage> <constraint> <regex>(\d+)(\.\d+)?[nNsS]</regex> </constraint> </properties> </leafNode> <leafNode name="longitude"> <properties> <help>Longitude</help> <valueHelp> <format><longitude></format> <description>Longitude (example "122.267255W")</description> </valueHelp> <constraintErrorMessage>Longiture should be a number followed by E or W</constraintErrorMessage> <constraint> <regex>(\d+)(\.\d+)?[eEwW]</regex> </constraint> </properties> </leafNode> </children> </node> <leafNode name="elin"> <properties> <help>ECS ELIN (Emergency location identifier number)</help> <valueHelp> <format>u32:0-9999999999</format> <description>Emergency Call Service ELIN number (between 10-25 numbers)</description> </valueHelp> <constraint> <regex>[0-9]{10,25}</regex> </constraint> <constraintErrorMessage>ELIN number must be between 10-25 numbers</constraintErrorMessage> </properties> </leafNode> </children> </node> </children> </tagNode> <node name="legacy-protocols"> <properties> <help>Legacy (vendor specific) protocols</help> </properties> <children> <leafNode name="cdp"> <properties> <help>Listen for CDP for Cisco routers/switches</help> <valueless/> </properties> </leafNode> <leafNode name="edp"> <properties> <help>Listen for EDP for Extreme routers/switches</help> <valueless/> </properties> </leafNode> <leafNode name="fdp"> <properties> <help>Listen for FDP for Foundry routers/switches</help> <valueless/> </properties> </leafNode> <leafNode name="sonmp"> <properties> <help>Listen for SONMP for Nortel routers/switches</help> <valueless/> </properties> </leafNode> </children> </node> <leafNode name="management-address"> <properties> <help>Management IP Address</help> <completionHelp> <script>${vyos_completion_dir}/list_local_ips.sh --both</script> </completionHelp> <valueHelp> <format>ipv4</format> <description>IPv4 Management Address</description> </valueHelp> <valueHelp> <format>ipv6</format> <description>IPv6 Management Address</description> </valueHelp> <constraint> <validator name="ip-address"/> </constraint> <multi/> </properties> </leafNode> - <node name="snmp"> + <leafNode name="snmp"> <properties> - <help>SNMP parameters for LLDP</help> + <help>Enable SNMP queries of the LLDP database</help> + <valueless/> </properties> - <children> - <leafNode name="enable"> - <properties> - <help>Enable SNMP queries of the LLDP database</help> - <valueless/> - </properties> - </leafNode> - </children> - </node> + </leafNode> </children> </node> </children> </node> </interfaceDefinition> diff --git a/smoketest/configs/ospf-small b/smoketest/configs/ospf-small index 767f4e21f..b3002b1af 100644 --- a/smoketest/configs/ospf-small +++ b/smoketest/configs/ospf-small @@ -1,158 +1,161 @@ interfaces { dummy dum0 { address 172.18.254.201/32 } ethernet eth0 { duplex auto smp-affinity auto speed auto vif 201 { address 172.18.201.10/24 ip { ospf { authentication { md5 { key-id 10 { md5-key OSPFVyOSNET } } } dead-interval 40 hello-interval 10 priority 1 retransmit-interval 5 transmit-delay 1 } } ipv6 { ospfv3 { bfd cost 40 } } } } ethernet eth1 { duplex auto smp-affinity auto speed auto ipv6 { ospfv3 { bfd cost 60 mtu-ignore network broadcast priority 20 } } } } protocols { ospf { area 0 { network 172.18.201.0/24 network 172.18.254.201/32 } log-adjacency-changes { } parameters { abr-type cisco router-id 172.18.254.201 } passive-interface default passive-interface-exclude eth0.201 } ospfv3 { area 0.0.0.0 { interface eth0 interface eth1 interface eth2 } } static { route 0.0.0.0/0 { next-hop 172.18.201.254 { distance 10 } } } } service { lldp { interface all { } + snmp { + enable + } } snmp { community public { authorization ro network 172.16.100.0/24 } contact "VyOS maintainers and contributors <maintainers@vyos.io>" location "Jenkins" } ssh { disable-host-validation port 22 } } system { config-management { commit-revisions 200 } console { device ttyS0 { speed 115200 } } domain-name vyos.net host-name vyos login { user vyos { authentication { encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/ plaintext-password "" } level admin } } name-server 172.16.254.30 ntp { server 0.pool.ntp.org { } server 1.pool.ntp.org { } server 2.pool.ntp.org { } } sysctl { all net.ipv4.conf.eth0.tag { value 1 } all net.ipv4.conf.eth1.tag { value 1 } custom net.mpls.default_ttl { value 10 } custom net.mpls.ip_ttl_propagate { value 0 } net.ipv4.igmp_max_memberships 5 net.ipv4.ipfrag_time 4 } syslog { global { facility all { level info } facility protocols { level debug } } } time-zone Europe/Berlin } /* Warning: Do not remove the following line. */ /* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ /* Release version: 1.2.6 */ diff --git a/smoketest/scripts/cli/test_service_lldp.py b/smoketest/scripts/cli/test_service_lldp.py index ee26844ab..7e30b43f5 100755 --- a/smoketest/scripts/cli/test_service_lldp.py +++ b/smoketest/scripts/cli/test_service_lldp.py @@ -1,127 +1,141 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import re import os import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.utils.process import cmd from vyos.utils.process import process_named_running from vyos.utils.file import read_file from vyos.version import get_version_data PROCESS_NAME = 'lldpd' LLDPD_CONF = '/etc/lldpd.d/01-vyos.conf' base_path = ['service', 'lldp'] mgmt_if = 'dum83513' mgmt_addr = ['1.2.3.4', '1.2.3.5'] class TestServiceLLDP(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): # call base-classes classmethod super(TestServiceLLDP, cls).setUpClass() # create a test interfaces for addr in mgmt_addr: cls.cli_set(cls, ['interfaces', 'dummy', mgmt_if, 'address', addr + '/32']) # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['interfaces', 'dummy', mgmt_if]) super(TestServiceLLDP, cls).tearDownClass() def tearDown(self): # service must be running after it was configured self.assertTrue(process_named_running(PROCESS_NAME)) # delete/stop LLDP service self.cli_delete(base_path) self.cli_commit() # service is no longer allowed to run after it was removed self.assertFalse(process_named_running(PROCESS_NAME)) def test_01_lldp_basic(self): self.cli_set(base_path) self.cli_commit() config = read_file(LLDPD_CONF) version_data = get_version_data() version = version_data['version'] self.assertIn(f'configure system platform VyOS', config) self.assertIn(f'configure system description "VyOS {version}"', config) def test_02_lldp_mgmt_address(self): for addr in mgmt_addr: self.cli_set(base_path + ['management-address', addr]) self.cli_commit() config = read_file(LLDPD_CONF) self.assertIn(f'configure system ip management pattern {",".join(mgmt_addr)}', config) def test_03_lldp_interfaces(self): for interface in Section.interfaces('ethernet'): if not '.' in interface: self.cli_set(base_path + ['interface', interface]) # commit changes self.cli_commit() # verify configuration config = read_file(LLDPD_CONF) interface_list = [] for interface in Section.interfaces('ethernet'): if not '.' in interface: interface_list.append(interface) tmp = ','.join(interface_list) self.assertIn(f'configure system interface pattern "{tmp}"', config) def test_04_lldp_all_interfaces(self): self.cli_set(base_path + ['interface', 'all']) # commit changes self.cli_commit() # verify configuration config = read_file(LLDPD_CONF) self.assertIn(f'configure system interface pattern "*"', config) def test_05_lldp_location(self): interface = 'eth0' elin = '1234567890' self.cli_set(base_path + ['interface', interface, 'location', 'elin', elin]) # commit changes self.cli_commit() # verify configuration config = read_file(LLDPD_CONF) self.assertIn(f'configure ports {interface} med location elin "{elin}"', config) self.assertIn(f'configure system interface pattern "{interface}"', config) + def test_06_lldp_snmp(self): + self.cli_set(base_path + ['snmp']) + + # verify - can not start lldp snmp without snmp beeing configured + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['service', 'snmp']) + self.cli_commit() + + # SNMP required process to be started with -x option + tmp = read_file('/etc/default/lldpd') + self.assertIn('-x', tmp) + + self.cli_delete(['service', 'snmp']) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/lldp.py b/src/conf_mode/lldp.py index c2e87d171..3c647a0e8 100755 --- a/src/conf_mode/lldp.py +++ b/src/conf_mode/lldp.py @@ -1,124 +1,123 @@ #!/usr/bin/env python3 # # Copyright (C) 2017-2022 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import os from sys import exit from vyos.base import Warning from vyos.config import Config from vyos.utils.network import is_addr_assigned from vyos.utils.network import is_loopback_addr from vyos.version import get_version_data from vyos.utils.process import call from vyos.utils.dict import dict_search from vyos.template import render from vyos import ConfigError from vyos import airbag airbag.enable() config_file = "/etc/default/lldpd" vyos_config_file = "/etc/lldpd.d/01-vyos.conf" base = ['service', 'lldp'] def get_config(config=None): if config: conf = config else: conf = Config() if not conf.exists(base): return {} lldp = conf.get_config_dict(base, key_mangling=('-', '_'), no_tag_node_value_mangle=True, get_first_key=True, with_recursive_defaults=True) if conf.exists(['service', 'snmp']): lldp['system_snmp_enabled'] = '' version_data = get_version_data() lldp['version'] = version_data['version'] # prune location information if not set by user for interface in lldp.get('interface', []): if lldp.from_defaults(['interface', interface, 'location']): del lldp['interface'][interface]['location'] elif lldp.from_defaults(['interface', interface, 'location','coordinate_based']): del lldp['interface'][interface]['location']['coordinate_based'] return lldp def verify(lldp): # bail out early - looks like removal from running config if lldp is None: return if 'management_address' in lldp: for address in lldp['management_address']: message = f'LLDP management address "{address}" is invalid' if is_loopback_addr(address): Warning(f'{message} - loopback address') elif not is_addr_assigned(address): Warning(f'{message} - not assigned to any interface') if 'interface' in lldp: for interface, interface_config in lldp['interface'].items(): # bail out early if no location info present in interface config if 'location' not in interface_config: continue if 'coordinate_based' in interface_config['location']: if not {'latitude', 'latitude'} <= set(interface_config['location']['coordinate_based']): raise ConfigError(f'Must define both longitude and latitude for "{interface}" location!') # check options - if 'snmp' in lldp and 'enable' in lldp['snmp']: + if 'snmp' in lldp: if 'system_snmp_enabled' not in lldp: - raise ConfigError('SNMP must be configured to enable LLDP SNMP') + raise ConfigError('SNMP must be configured to enable LLDP SNMP!') def generate(lldp): # bail out early - looks like removal from running config if lldp is None: return render(config_file, 'lldp/lldpd.j2', lldp) render(vyos_config_file, 'lldp/vyos.conf.j2', lldp) def apply(lldp): systemd_service = 'lldpd.service' if lldp: # start/restart lldp service call(f'systemctl restart {systemd_service}') else: # LLDP service has been terminated call(f'systemctl stop {systemd_service}') if os.path.isfile(config_file): os.unlink(config_file) if os.path.isfile(vyos_config_file): os.unlink(vyos_config_file) if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) exit(1) - diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index 38b5d52f9..f65d0d6bc 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -1,270 +1,270 @@ #!/usr/bin/env python3 # # Copyright (C) 2018-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import os from sys import exit from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge from vyos.configverify import verify_vrf from vyos.snmpv3_hashgen import plaintext_to_md5 from vyos.snmpv3_hashgen import plaintext_to_sha1 from vyos.snmpv3_hashgen import random from vyos.template import render from vyos.utils.process import call from vyos.utils.permission import chmod_755 from vyos.utils.dict import dict_search from vyos.utils.network import is_addr_assigned from vyos.version import get_version_data from vyos import ConfigError from vyos import airbag airbag.enable() config_file_client = r'/etc/snmp/snmp.conf' config_file_daemon = r'/etc/snmp/snmpd.conf' config_file_access = r'/usr/share/snmp/snmpd.conf' config_file_user = r'/var/lib/snmp/snmpd.conf' systemd_override = r'/run/systemd/system/snmpd.service.d/override.conf' systemd_service = 'snmpd.service' def get_config(config=None): if config: conf = config else: conf = Config() base = ['service', 'snmp'] snmp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) if not conf.exists(base): snmp.update({'deleted' : ''}) - if conf.exists(['service', 'lldp', 'snmp', 'enable']): + if conf.exists(['service', 'lldp', 'snmp']): snmp.update({'lldp_snmp' : ''}) if 'deleted' in snmp: return snmp version_data = get_version_data() snmp['version'] = version_data['version'] # create an internal snmpv3 user of the form 'vyosxxxxxxxxxxxxxxxx' snmp['vyos_user'] = 'vyos' + random(8) snmp['vyos_user_pass'] = random(16) # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. snmp = conf.merge_defaults(snmp, recursive=True) if 'listen_address' in snmp: # Always listen on localhost if an explicit address has been configured # This is a safety measure to not end up with invalid listen addresses # that are not configured on this system. See https://vyos.dev/T850 if '127.0.0.1' not in snmp['listen_address']: tmp = {'127.0.0.1': {'port': '161'}} snmp['listen_address'] = dict_merge(tmp, snmp['listen_address']) if '::1' not in snmp['listen_address']: tmp = {'::1': {'port': '161'}} snmp['listen_address'] = dict_merge(tmp, snmp['listen_address']) return snmp def verify(snmp): if 'deleted' in snmp: return None if {'deleted', 'lldp_snmp'} <= set(snmp): raise ConfigError('Can not delete SNMP service, as LLDP still uses SNMP!') ### check if the configured script actually exist if 'script_extensions' in snmp and 'extension_name' in snmp['script_extensions']: for extension, extension_opt in snmp['script_extensions']['extension_name'].items(): if 'script' not in extension_opt: raise ConfigError(f'Script extension "{extension}" requires an actual script to be configured!') tmp = extension_opt['script'] if not os.path.isfile(tmp): Warning(f'script "{tmp}" does not exist!') else: chmod_755(extension_opt['script']) if 'listen_address' in snmp: for address in snmp['listen_address']: # We only wan't to configure addresses that exist on the system. # Hint the user if they don't exist if 'vrf' in snmp: vrf_name = snmp['vrf'] if not is_addr_assigned(address, vrf_name) and address not in ['::1','127.0.0.1']: raise ConfigError(f'SNMP listen address "{address}" not configured in vrf "{vrf_name}"!') elif not is_addr_assigned(address): raise ConfigError(f'SNMP listen address "{address}" not configured in default vrf!') if 'trap_target' in snmp: for trap, trap_config in snmp['trap_target'].items(): if 'community' not in trap_config: raise ConfigError(f'Trap target "{trap}" requires a community to be set!') if 'oid_enable' in snmp: Warning(f'Custom OIDs are enabled and may lead to system instability and high resource consumption') verify_vrf(snmp) # bail out early if SNMP v3 is not configured if 'v3' not in snmp: return None if 'user' in snmp['v3']: for user, user_config in snmp['v3']['user'].items(): if 'group' not in user_config: raise ConfigError(f'Group membership required for user "{user}"!') if 'plaintext_password' not in user_config['auth'] and 'encrypted_password' not in user_config['auth']: raise ConfigError(f'Must specify authentication encrypted-password or plaintext-password for user "{user}"!') if 'plaintext_password' not in user_config['privacy'] and 'encrypted_password' not in user_config['privacy']: raise ConfigError(f'Must specify privacy encrypted-password or plaintext-password for user "{user}"!') if 'group' in snmp['v3']: for group, group_config in snmp['v3']['group'].items(): if 'seclevel' not in group_config: raise ConfigError(f'Must configure "seclevel" for group "{group}"!') if 'view' not in group_config: raise ConfigError(f'Must configure "view" for group "{group}"!') # Check if 'view' exists view = group_config['view'] if 'view' not in snmp['v3'] or view not in snmp['v3']['view']: raise ConfigError(f'You must create view "{view}" first!') if 'view' in snmp['v3']: for view, view_config in snmp['v3']['view'].items(): if 'oid' not in view_config: raise ConfigError(f'Must configure an "oid" for view "{view}"!') if 'trap_target' in snmp['v3']: for trap, trap_config in snmp['v3']['trap_target'].items(): if 'plaintext_password' not in trap_config['auth'] and 'encrypted_password' not in trap_config['auth']: raise ConfigError(f'Must specify one of authentication encrypted-password or plaintext-password for trap "{trap}"!') if {'plaintext_password', 'encrypted_password'} <= set(trap_config['auth']): raise ConfigError(f'Can not specify both authentication encrypted-password and plaintext-password for trap "{trap}"!') if 'plaintext_password' not in trap_config['privacy'] and 'encrypted_password' not in trap_config['privacy']: raise ConfigError(f'Must specify one of privacy encrypted-password or plaintext-password for trap "{trap}"!') if {'plaintext_password', 'encrypted_password'} <= set(trap_config['privacy']): raise ConfigError(f'Can not specify both privacy encrypted-password and plaintext-password for trap "{trap}"!') if 'type' not in trap_config: raise ConfigError('SNMP v3 trap "type" must be specified!') return None def generate(snmp): # As we are manipulating the snmpd user database we have to stop it first! # This is even save if service is going to be removed call(f'systemctl stop {systemd_service}') # Clean config files config_files = [config_file_client, config_file_daemon, config_file_access, config_file_user, systemd_override] for file in config_files: if os.path.isfile(file): os.unlink(file) if 'deleted' in snmp: return None if 'v3' in snmp: # net-snmp is now regenerating the configuration file in the background # thus we need to re-open and re-read the file as the content changed. # After that we can no read the encrypted password from the config and # replace the CLI plaintext password with its encrypted version. os.environ['vyos_libexec_dir'] = '/usr/libexec/vyos' if 'user' in snmp['v3']: for user, user_config in snmp['v3']['user'].items(): if dict_search('auth.type', user_config) == 'sha': hash = plaintext_to_sha1 else: hash = plaintext_to_md5 if dict_search('auth.plaintext_password', user_config) is not None: tmp = hash(dict_search('auth.plaintext_password', user_config), dict_search('v3.engineid', snmp)) snmp['v3']['user'][user]['auth']['encrypted_password'] = tmp del snmp['v3']['user'][user]['auth']['plaintext_password'] call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" auth encrypted-password "{tmp}" > /dev/null') call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" auth plaintext-password > /dev/null') if dict_search('privacy.plaintext_password', user_config) is not None: tmp = hash(dict_search('privacy.plaintext_password', user_config), dict_search('v3.engineid', snmp)) snmp['v3']['user'][user]['privacy']['encrypted_password'] = tmp del snmp['v3']['user'][user]['privacy']['plaintext_password'] call(f'/opt/vyatta/sbin/my_set service snmp v3 user "{user}" privacy encrypted-password "{tmp}" > /dev/null') call(f'/opt/vyatta/sbin/my_delete service snmp v3 user "{user}" privacy plaintext-password > /dev/null') # Write client config file render(config_file_client, 'snmp/etc.snmp.conf.j2', snmp) # Write server config file render(config_file_daemon, 'snmp/etc.snmpd.conf.j2', snmp) # Write access rights config file render(config_file_access, 'snmp/usr.snmpd.conf.j2', snmp) # Write access rights config file render(config_file_user, 'snmp/var.snmpd.conf.j2', snmp) # Write daemon configuration file render(systemd_override, 'snmp/override.conf.j2', snmp) return None def apply(snmp): # Always reload systemd manager configuration call('systemctl daemon-reload') if 'deleted' in snmp: return None # start SNMP daemon call(f'systemctl restart {systemd_service}') # Enable AgentX in FRR # This should be done for each daemon individually because common command # works only if all the daemons started with SNMP support frr_daemons_list = [ 'bgpd', 'ospf6d', 'ospfd', 'ripd', 'ripngd', 'isisd', 'ldpd', 'zebra' ] for frr_daemon in frr_daemons_list: call(f'vtysh -c "configure terminal" -d {frr_daemon} -c "agentx" >/dev/null') return None if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) exit(1) diff --git a/src/migration-scripts/lldp/0-to-1 b/src/migration-scripts/lldp/0-to-1 index a936cbdfc..a99356062 100755 --- a/src/migration-scripts/lldp/0-to-1 +++ b/src/migration-scripts/lldp/0-to-1 @@ -1,35 +1,49 @@ #!/usr/bin/env python3 +# +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. # Delete "set service lldp interface <interface> location civic-based" option # as it was broken most of the time anyways import sys from vyos.configtree import ConfigTree if len(sys.argv) < 2: print("Must specify file name!") sys.exit(1) file_name = sys.argv[1] with open(file_name, 'r') as f: config_file = f.read() config = ConfigTree(config_file) base = ['service', 'lldp', 'interface'] if not config.exists(base): # Nothing to do sys.exit(0) else: # Delete nodes with abandoned CLI syntax for interface in config.list_nodes(base): if config.exists(base + [interface, 'location', 'civic-based']): config.delete(base + [interface, 'location', 'civic-based']) try: with open(file_name, 'w') as f: f.write(config.to_string()) except OSError as e: print("Failed to save the modified config: {}".format(e)) sys.exit(1) diff --git a/src/migration-scripts/lldp/1-to-2 b/src/migration-scripts/lldp/1-to-2 new file mode 100755 index 000000000..35efb25db --- /dev/null +++ b/src/migration-scripts/lldp/1-to-2 @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# T5855: migrate "set service lldp snmp enable" -> `set service lldp snmp" + +import sys + +from vyos.configtree import ConfigTree + +if len(sys.argv) < 2: + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) +base = ['service', 'lldp'] +if not config.exists(base): + # Nothing to do + sys.exit(0) + +if config.exists(base + ['snmp']): + enabled = config.exists(base + ['snmp', 'enable']) + config.delete(base + ['snmp']) + if enabled: config.set(base + ['snmp']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1)