From 6b7c267de3272bff74fa1321f81559d24a85f13f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 1 Sep 2018 21:18:32 +0200 Subject: snmp.py: improve daemon startup The previous implementation used a hardcoded 2 seconds sleep until the daemon configuration was rendered by snmpd (user/password stuff). Waiting 2 seconds is error prone and was replaced by reading the configuration file until it shows a marker indicating that the file was properly processed by snmpd. --- src/conf_mode/snmp.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py index a4e776d49..3eb2935be 100755 --- a/src/conf_mode/snmp.py +++ b/src/conf_mode/snmp.py @@ -21,7 +21,6 @@ import os import shutil import stat import pwd -import time import jinja2 import random @@ -771,9 +770,17 @@ def apply(snmp): # start SNMP daemon os.system("sudo systemctl restart snmpd.service") - # the passwords are not available immediately so this is a workaround - # and should be changed to polling - time.sleep(2) + # Passwords are not available immediately in the configuration file, + # after daemon startup - we wait until they have been processed by + # snmpd, which we see when a magic line appears in this file. + snmpReady = False + while not snmpReady: + with open(config_file_user, 'r') as f: + for line in f: + # Search for our magic string inside the file + if '**** DO NOT EDIT THIS FILE ****' in line: + snmpReady = True + break # Back in the Perl days the configuration was re-read and any # plaintext password inside the configuration was replaced by -- cgit v1.2.3 From eb4e34c967c9ac638605723cb9b5dfbebc5d6253 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Sun, 2 Sep 2018 00:46:22 +0200 Subject: T822: add sudo to tcpdump commands to make them use correct PATH now, and to enable getting rid of capabilities later. --- op-mode-definitions/traffic-dump.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/op-mode-definitions/traffic-dump.xml b/op-mode-definitions/traffic-dump.xml index a6810644e..00a809a7c 100644 --- a/op-mode-definitions/traffic-dump.xml +++ b/op-mode-definitions/traffic-dump.xml @@ -8,7 +8,7 @@ - tcpdump -i $4 + sudo tcpdump -i $4 Monitor traffic dump from an interface @@ -17,19 +17,19 @@ - tcpdump -n -i $4 $6 + sudo tcpdump -n -i $4 $6 Monitor traffic matching filter conditions - tcpdump -n -i $4 -w $6 + sudo tcpdump -n -i $4 -w $6 Save traffic dump from an interface to a file - tcpdump -n -i $4 -w $6 $8 + sudo tcpdump -n -i $4 -w $6 $8 Save a dump of traffic matching filter conditions to a file -- cgit v1.2.3 From 4d36b564d468628cb87f92f1054d6ff8d499087e Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Sun, 2 Sep 2018 01:34:48 +0200 Subject: T823: add dependency on python3-six This is needed because this dependency is missing in the python3--isc-dhcp-leases package from stretch. When that issue is resolved, the dependency can be safely removed. --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 04b228737..b20690e75 100644 --- a/debian/control +++ b/debian/control @@ -21,6 +21,7 @@ Depends: python3, python3-pystache, python3-psutil, python3-tabulate, + python3-six, ipaddrcheck, tcpdump, bmon, -- cgit v1.2.3 From 55f464ab2642951251da668d3d38d7dad9225629 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Sun, 2 Sep 2018 01:35:53 +0200 Subject: T823: add a new DHCP op mode script, only capable of showing leases now. --- src/op_mode/show_dhcp.py | 104 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100755 src/op_mode/show_dhcp.py diff --git a/src/op_mode/show_dhcp.py b/src/op_mode/show_dhcp.py new file mode 100755 index 000000000..2084a9509 --- /dev/null +++ b/src/op_mode/show_dhcp.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import argparse + +import tabulate + +import vyos.config + +from isc_dhcp_leases import Lease, IscDhcpLeases + + +lease_file = "/config/dhcpd.leases" +pool_key = "shared-networkname" + +def in_pool(lease, pool): + if pool_key in lease.sets: + if lease.sets[pool_key] == pool: + return True + + return False + +def get_lease_data(lease): + data = {} + + # End time may not be present in backup leases + try: + data["expires"] = lease.end.strftime("%Y/%m/%d %H:%M:%S") + except: + data["expires"] = "" + + data["hardware_address"] = lease.ethernet + data["hostname"] = lease.hostname + data["ip"] = lease.ip + + try: + data["pool"] = lease.sets[pool_key] + except: + data["pool"] = "" + + return data + +def get_leases(leases, state=None, pool=None): + leases = IscDhcpLeases(lease_file).get() + + if state is not None: + leases = list(filter(lambda x: x.binding_state == 'active', leases)) + + if pool is not None: + leases = list(filter(lambda x: in_pool(x, pool), leases)) + + return list(map(get_lease_data, leases)) + +def show_leases(leases): + headers = ["IP address", "Hardware address", "Lease expiration", "Pool", "Client Name"] + + lease_list = [] + for l in leases: + lease_list.append([l["ip"], l["hardware_address"], l["expires"], l["pool"], l["hostname"]]) + + output = tabulate.tabulate(lease_list, headers) + + print(output) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + + group = parser.add_mutually_exclusive_group() + group.add_argument("-l", "--leases", action="store_true", help="Show DHCP leases") + group.add_argument("-s", "--statistics", action="store_true", help="Show DHCP statistics") + + parser.add_argument("-e", "--expired", action="store_true", help="Show expired leases") + parser.add_argument("-p", "--pool", type=str, action="store", help="Show lease for specific pool") + + args = parser.parse_args() + + if args.leases: + if args.expired: + if args.pool: + leases = get_leases(lease_file, state='free', pool=args.pool) + else: + leases = get_leases(lease_file, state='free') + else: + if args.pool: + leases = get_leases(lease_file, state='active', pool=args.pool) + else: + leases = get_leases(lease_file, state='active') + + show_leases(leases) -- cgit v1.2.3 From 4532e0bad9208235e09ef2bf29586b00ea4b998d Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 2 Sep 2018 12:23:25 +0200 Subject: mdns_repeater: cleanup python implementation --- src/conf_mode/mdns_repeater.py | 92 +++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 37 deletions(-) diff --git a/src/conf_mode/mdns_repeater.py b/src/conf_mode/mdns_repeater.py index 474a6a5cf..cef735c0d 100755 --- a/src/conf_mode/mdns_repeater.py +++ b/src/conf_mode/mdns_repeater.py @@ -18,7 +18,7 @@ import sys import os - +import jinja2 import netifaces from vyos.config import Config @@ -26,60 +26,78 @@ from vyos import ConfigError config_file = r'/etc/default/mdns-repeater' -def get_config(): - interface_list = [] +config_tmpl = """ +### Autogenerated by mdns_repeater.py ### +DAEMON_ARGS="{{ interfaces | join(' ') }}" +""" + +default_config_data = { + 'disabled': False, + 'interfaces': [] +} +def get_config(): + mdns = default_config_data conf = Config() - conf.set_level('service mdns repeater') - if not conf.exists(''): - return interface_list + if not conf.exists('service mdns repeater'): + return None + else: + conf.set_level('service mdns repeater') - if conf.exists('interface'): - intfs_names = [] - intfs_names = conf.return_values('interface') + # Service can be disabled by user + if conf.exists('disable'): + mdns['disabled'] = True + return mdns - for name in intfs_names: - interface_list.append(name) + # Interface to repeat mDNS advertisements + if conf.exists('interface'): + mdns['interfaces'] = conf.return_values('interface') - return interface_list + return mdns def verify(mdns): - # '0' interfaces are possible, think of service deletion. Only '1' is not supported! - if len(mdns) == 1: - raise ConfigError('At least 2 interfaces must be specified but %d given!' % len(mdns)) - - # For mdns-repeater to work it is essential that the interfaces - # have an IP address assigned - for intf in mdns: - try: - netifaces.ifaddresses(intf)[netifaces.AF_INET] - except KeyError as e: - raise ConfigError('No IP address configured for interface "%s"!' % intf) + if mdns is None: + return None + + if mdns['disabled']: + return None + + # We need at least two interfaces to repeat mDNS advertisments + if len(mdns['interfaces']) < 2: + raise ConfigError('mDNS repeater requires at least 2 configured interfaces!') + + # For mdns-repeater to work it is essential that the interfaces has + # an IPv4 address assigned + for interface in mdns['interfaces']: + if netifaces.AF_INET in netifaces.ifaddresses(interface).keys(): + if len(netifaces.ifaddresses(interface)[netifaces.AF_INET]) < 1: + raise ConfigError('mDNS repeater requires an IPv6 address configured on interface %s!'.format(interface)) return None def generate(mdns): - config_header = '### Autogenerated by mdns_repeater.py ###\n' - if len(mdns) > 0: - config_args = 'DAEMON_ARGS="' + ' '.join(str(e) for e in mdns) + '"\n' - else: - config_args = 'DAEMON_ARGS=""\n' + if mdns is None: + return None + + if mdns['disabled']: + print('Warning: mDNS repeater will be deactivated because it is disabled') + return None - # write new configuration file - f = open(config_file, 'w') - f.write(config_header) - f.write(config_args) - f.close() + tmpl = jinja2.Template(config_tmpl) + config_text = tmpl.render(mdns) + with open(config_file, 'w') as f: + f.write(config_text) return None def apply(mdns): - if len(mdns) == 0: - cmd = "sudo systemctl stop mdns-repeater" + if (mdns is None) or mdns['disabled']: + os.system('sudo systemctl stop mdns-repeater') + if os.path.exists(config_file): + os.unlink(config_file) else: - cmd = "sudo systemctl restart mdns-repeater" + os.system('sudo systemctl restart mdns-repeater') - os.system(cmd) return None if __name__ == '__main__': -- cgit v1.2.3 From 0a1f99d8d61169abd92072d2884bb9d2d76833b8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 2 Sep 2018 12:23:38 +0200 Subject: mdns_repeater: add 'disable' option --- interface-definitions/mdns-repeater.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/interface-definitions/mdns-repeater.xml b/interface-definitions/mdns-repeater.xml index d74e203d6..a59321294 100644 --- a/interface-definitions/mdns-repeater.xml +++ b/interface-definitions/mdns-repeater.xml @@ -14,9 +14,15 @@ 990 + + + Disable mDNS repeater service + + + - Interface to repeat mdns advertisements to [REQUIRED] + Interface to repeat mDNS advertisements [REQUIRED] -- cgit v1.2.3 From 327ca85aa391b6a3c75f1da04b3d71c61869d90c Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Sun, 2 Sep 2018 13:07:27 +0200 Subject: T824: add Python bindings for the rename_node function to vyos.configtree --- python/vyos/configtree.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index 4b46a1fb3..ad8fcef1a 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -112,6 +112,10 @@ class ConfigTree(object): self.__delete.argtypes = [c_void_p, c_char_p] self.__delete.restype = c_int + self.__rename = self.__lib.rename_node + self.__rename.argtypes = [c_void_p, c_char_p, c_char_p] + self.__rename.restype = c_int + self.__set_replace_value = self.__lib.set_replace_value self.__set_replace_value.argtypes = [c_void_p, c_char_p, c_char_p] self.__set_replace_value.restype = c_int @@ -193,6 +197,13 @@ class ConfigTree(object): self.__delete_value(self.__config, path_str, value.encode()) + def rename(self, path, newname): + check_path(path) + path_str = " ".join(map(str, path)).encode() + newname_str = newname.encode() + + self.__rename(self.__config, path_str, newname_str) + def exists(self, path): check_path(path) path_str = " ".join(map(str, path)).encode() -- cgit v1.2.3 From c49ec1392ba68a20af13c21f8a739d7b1dbc4906 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 2 Sep 2018 13:30:38 +0200 Subject: T825: add system 8-to-9 migration script --- src/migration-scripts/system/8-to-9 | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100755 src/migration-scripts/system/8-to-9 diff --git a/src/migration-scripts/system/8-to-9 b/src/migration-scripts/system/8-to-9 new file mode 100755 index 000000000..db3fefdea --- /dev/null +++ b/src/migration-scripts/system/8-to-9 @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +# Deletes "system package" option as it is deprecated + +import sys + +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +if not config.exists(['system', 'package']): + # Nothing to do + sys.exit(0) +else: + # Delete the node with the old syntax + config.delete(['system', 'package']) + + 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) -- cgit v1.2.3