#!/usr/bin/env python3 # # Copyright (C) 2017-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 . import os import fnmatch from sys import exit from copy import deepcopy from vyos.config import Config from vyos import ConfigError from vyos.util import call from vyos.template import render config_file = r'/etc/default/udp-broadcast-relay' default_config_data = { 'disabled': False, 'instances': [] } def get_config(): relay = deepcopy(default_config_data) conf = Config() base = ['service', 'broadcast-relay'] if not conf.exists(base): return None else: conf.set_level(base) # Service can be disabled by user if conf.exists('disable'): relay['disabled'] = True return relay # Parse configuration of each individual instance if conf.exists('id'): for id in conf.list_nodes('id'): conf.set_level(base + ['id', id]) config = { 'id': id, 'disabled': False, 'address': '', 'description': '', 'interfaces': [], 'port': '' } # Check if individual broadcast relay service is disabled if conf.exists(['disable']): config['disabled'] = True # Source IP of forwarded packets, if empty original senders address is used if conf.exists(['address']): config['address'] = conf.return_value(['address']) # A description for each individual broadcast relay service if conf.exists(['description']): config['description'] = conf.return_value(['description']) # UDP port to listen on for broadcast frames if conf.exists(['port']): config['port'] = conf.return_value(['port']) # Network interfaces to listen on for broadcast frames to be relayed if conf.exists(['interface']): config['interfaces'] = conf.return_values(['interface']) relay['instances'].append(config) return relay def verify(relay): if relay is None: return None if relay['disabled']: return None for r in relay['instances']: # we don't have to check this instance when it's disabled if r['disabled']: continue # we certainly require a UDP port to listen to if not r['port']: raise ConfigError('UDP broadcast relay "{0}" requires a port number'.format(r['id'])) # Relaying data without two interface is kinda senseless ... if len(r['interfaces']) < 2: raise ConfigError('UDP broadcast relay "id {0}" requires at least 2 interfaces'.format(r['id'])) return None def generate(relay): if relay is None: return None config_dir = os.path.dirname(config_file) config_filename = os.path.basename(config_file) active_configs = [] for config in fnmatch.filter(os.listdir(config_dir), config_filename + '*'): # determine prefix length to identify service instance prefix_len = len(config_filename) active_configs.append(config[prefix_len:]) # sort our list active_configs.sort() # delete old configuration files for id in active_configs[:]: if os.path.exists(config_file + id): os.unlink(config_file + id) # If the service is disabled, we can bail out here if relay['disabled']: print('Warning: UDP broadcast relay service will be deactivated because it is disabled') return None for r in relay['instances']: # Skip writing instance config when it's disabled if r['disabled']: continue # configuration filename contains instance id file = config_file + str(r['id']) render(file, 'bcast-relay/udp-broadcast-relay.tmpl', r) return None def apply(relay): # first stop all running services call('systemctl stop udp-broadcast-relay@{1..99}.service') if (relay is None) or relay['disabled']: return None # start only required service instances for r in relay['instances']: # Don't start individual instance when it's disabled if r['disabled']: continue call('systemctl start udp-broadcast-relay@{0}.service'.format(r['id'])) return None if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) exit(1)