diff --git a/hb_config.py b/hb_config.py index 62b1d71..522902f 100644 --- a/hb_config.py +++ b/hb_config.py @@ -15,8 +15,7 @@ def build_config(_config_file): CONFIG['GLOBAL'] = {} CONFIG['LOGGER'] = {} CONFIG['AMBE'] = {} - CONFIG['CLIENTS'] = {} - CONFIG['MASTERS'] = {} + CONFIG['SYSTEMS'] = {} try: for section in config.sections(): @@ -48,7 +47,8 @@ def build_config(_config_file): elif config.getboolean(section, 'ENABLED'): # HomeBrew Client (Repeater) Configuration(s) if config.get(section, 'MODE') == 'CLIENT': - CONFIG['CLIENTS'].update({section: { + CONFIG['SYSTEMS'].update({section: { + 'MODE': config.get(section, 'MODE'), 'ENABLED': config.getboolean(section, 'ENABLED'), 'EXPORT_AMBE': config.getboolean(section, 'EXPORT_AMBE'), 'IP': gethostbyname(config.get(section, 'IP')), @@ -72,7 +72,7 @@ def build_config(_config_file): 'SOFTWARE_ID': config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'PACKAGE_ID': config.get(section, 'PACKAGE_ID').ljust(40)[:40] }}) - CONFIG['CLIENTS'][section].update({'STATS': { + CONFIG['SYSTEMS'][section].update({'STATS': { 'CONNECTION': 'NO', # NO, RTPL_SENT, AUTHENTICATED, CONFIG-SENT, YES 'PINGS_SENT': 0, 'PINGS_ACKD': 0, @@ -83,7 +83,8 @@ def build_config(_config_file): elif config.get(section, 'MODE') == 'MASTER': # HomeBrew Master Configuration - CONFIG['MASTERS'].update({section: { + CONFIG['SYSTEMS'].update({section: { + 'MODE': config.get(section, 'MODE'), 'ENABLED': config.getboolean(section, 'ENABLED'), 'REPEAT': config.getboolean(section, 'REPEAT'), 'EXPORT_AMBE': config.getboolean(section, 'EXPORT_AMBE'), @@ -91,11 +92,39 @@ def build_config(_config_file): 'PORT': config.getint(section, 'PORT'), 'PASSPHRASE': config.get(section, 'PASSPHRASE') }}) - CONFIG['MASTERS'][section].update({'CLIENTS': {}}) + CONFIG['SYSTEMS'][section].update({'CLIENTS': {}}) except ConfigParser.Error, err: # Very simple error reporting print "Cannot parse configuration file. %s" %err sys.exit('Could not parse configuration file, exiting...') - return CONFIG \ No newline at end of file + return CONFIG + + + + + +# Used to run this file direclty and print the config, +# which might be useful for debugging +if __name__ == '__main__': + import sys + import os + import argparse + from pprint import pprint + + # Change the current directory to the location of the application + os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) + + # CLI argument parser - handles picking up the config file from the command line, and sending a "help" message + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually hblink.cfg)') + cli_args = parser.parse_args() + + + # Ensure we have a path for the config file, if one wasn't specified, then use the execution directory + if not cli_args.CONFIG_FILE: + cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg' + + + pprint(build_config(cli_args.CONFIG_FILE)) \ No newline at end of file diff --git a/hb_router.py b/hb_router.py index 53e44bf..7a3e149 100755 --- a/hb_router.py +++ b/hb_router.py @@ -20,7 +20,7 @@ from twisted.internet import reactor from twisted.internet import task # Things we import from the main hblink module -from hblink import CONFIG, HBMASTER, HBCLIENT, logger, masters, clients, hex_str_3, int_id +from hblink import CONFIG, HBMASTER, HBCLIENT, logger, systems, hex_str_3, int_id # Import Bridging rules # Note: A stanza *must* exist for any MASTER or CLIENT configured in the main @@ -34,8 +34,8 @@ except ImportError: # Convert integer GROUP ID numbers from the config into hex strings # we need to send in the actual data packets. -for _system in RULES_FILE['MASTERS']: - for _rule in RULES_FILE['MASTERS'][_system]['GROUP_VOICE']: +for _system in RULES_FILE: + for _rule in RULES_FILE[_system]['GROUP_VOICE']: _rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP']) _rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP']) _rule['SRC_TS'] = _rule['SRC_TS'] @@ -44,27 +44,11 @@ for _system in RULES_FILE['MASTERS']: _rule['ON'][i] = hex_str_3(_rule['ON'][i]) for i, e in enumerate(_rule['OFF']): _rule['OFF'][i] = hex_str_3(_rule['OFF'][i]) - if _system not in CONFIG['MASTERS']: - sys.exit('ERROR: Routing rules found for MASTER system not configured in main configuration') -for _system in CONFIG['MASTERS']: - if _system not in RULES_FILE['MASTERS']: - sys.exit('ERROR: Routing rules not found for all MASTER systems configured') - -for _system in RULES_FILE['CLIENTS']: - for _rule in RULES_FILE['CLIENTS'][_system]['GROUP_VOICE']: - _rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP']) - _rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP']) - _rule['SRC_TS'] = _rule['SRC_TS'] - _rule['DST_TS'] = _rule['DST_TS'] - for i, e in enumerate(_rule['ON']): - _rule['ON'][i] = hex_str_3(_rule['ON'][i]) - for i, e in enumerate(_rule['OFF']): - _rule['OFF'][i] = hex_str_3(_rule['OFF'][i]) - if _system not in CONFIG['CLIENTS']: - sys.exit('ERROR: Routing rules found for CLIENT system not configured in main configuration') -for _system in CONFIG['CLIENTS']: - if _system not in RULES_FILE['CLIENTS']: - sys.exit('ERROR: Routing rules not found for all CLIENT systems configured') + if _system not in CONFIG['SYSTEMS']: + sys.exit('ERROR: Routing rules found for system not configured main configuration') +for _system in CONFIG['SYSTEMS']: + if _system not in RULES_FILE: + sys.exit('ERROR: Routing rules not found for all systems configured') RULES = RULES_FILE @@ -84,15 +68,11 @@ __status__ = 'pre-alpha' class routerMASTER(HBMASTER): def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _data): - for rule in RULES['MASTERS'][self._master]['GROUP_VOICE']: + for rule in RULES[self._master]['GROUP_VOICE']: _target = rule['DST_NET'] - if _target in RULES['MASTERS']: - masters[_target].send_clients(_data) - logger.debug('(%s) Packet routed to master instance: %s', self._master, _target) - - elif _target in RULES['CLIENTS']: - clients[_target].send_packet(_data) - logger.debug('(%s) Packet routed to client instance: %s', self._master, _target) + if _target in RULES: + systems[_target].send_system(_data) + logger.debug('(%s) Packet routed %s to system: %s', self._master, CONFIG[_target]['MODE'], _target) else: logger.debug('(%s) Packet router found no target for packet. Destination was: %s on target network %s', self._master, _dst_id, _target) @@ -102,15 +82,11 @@ class routerMASTER(HBMASTER): class routerCLIENT(HBCLIENT): def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _data): - for rule in RULES['CLIENTS'][self._client]['GROUP_VOICE']: + for rule in RULES[self._client]['GROUP_VOICE']: _target = rule['DST_NET'] - if _target in RULES['MASTERS']: - masters[_target].send_clients(_data) - logger.debug('(%s) Packet routed to master instance: %s', self._client, _target) - - elif _target in RULES['CLIENTS']: - clients[_target].send_packet(_data) - logger.debug('(%s) Packet routed to client instance: %s', self._client, _target) + if _target in RULES: + system[_target].send_system(_data) + logger.debug('(%s) Packet routed to %s system: %s', self._client, CONFIG[_target]['MODE'], _target) else: logger.debug('(%s) Packet router found no target for packet. Destination was: %s on target network %s', self._client, _dst_id, _target) @@ -123,17 +99,14 @@ class routerCLIENT(HBCLIENT): if __name__ == '__main__': logger.info('HBlink \'hb_router.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...') - # HBlink Master - for master in CONFIG['MASTERS']: - if CONFIG['MASTERS'][master]['ENABLED']: - masters[master] = routerMASTER(master) - reactor.listenUDP(CONFIG['MASTERS'][master]['PORT'], masters[master], interface=CONFIG['MASTERS'][master]['IP']) - logger.debug('MASTER instance created: %s, %s', master, masters[master]) - - for client in CONFIG['CLIENTS']: - if CONFIG['CLIENTS'][client]['ENABLED']: - clients[client] = routerCLIENT(client) - reactor.listenUDP(CONFIG['CLIENTS'][client]['PORT'], clients[client], interface=CONFIG['CLIENTS'][client]['IP']) - logger.debug('CLIENT instance created: %s, %s', client, clients[client]) + # HBlink instance creation + for system in CONFIG['SYSTEMS']: + if CONFIG['SYSTEMS'][system]['ENABLED']: + if CONFIG['SYSTEMS'][system]['MODE'] == 'MASTER': + systems[system] = HBMASTER(system) + elif CONFIG['SYSTEMS'][system]['MODE'] == 'CLIENT': + systems[system] = HBCLIENT(system) + reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) + logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) reactor.run() \ No newline at end of file diff --git a/hb_routing_rules.py b/hb_routing_rules.py index 9649108..164a495 100644 --- a/hb_routing_rules.py +++ b/hb_routing_rules.py @@ -1,28 +1,28 @@ RULES = { - 'MASTERS': { - 'MASTER-1': { - 'GROUP_HANGTIME': 5, - 'GROUP_VOICE': [ - {'NAME': 'STATEWIDE', 'ACTIVE': False, 'ON': [8,], 'OFF': [9,10], 'SRC_TS': 1, 'SRC_GROUP': 1, 'DST-TYPE': 'CLIENT', 'DST_NET': 'REPEATER-1', 'DST_TS': 2, 'DST_GROUP': 2}, - # When DMRD received on this MASTER, Time Slot 1, Talk Group 1; send to CLIENT-1 on Time Slot 2 Talk Group 2 - # This rule is NOT enabled by default - # This rule can be enabled by transmitting on TGID 8 - # This rule can be disabled by transmitting on TGID 9 or 10 - # Repeat the above line for as many rules for this IPSC network as you want. - ] - }, + 'MASTER-1': { + 'GROUP_HANGTIME': 5, + 'GROUP_VOICE': [ + {'NAME': 'STATEWIDE', 'ACTIVE': False, 'ON': [8,], 'OFF': [9,10], 'SRC_TS': 1, 'SRC_GROUP': 1, 'DST-TYPE': 'CLIENT', 'DST_NET': 'REPEATER-1', 'DST_TS': 2, 'DST_GROUP': 2}, + # When DMRD received on this MASTER, Time Slot 1, Talk Group 1; send to CLIENT-1 on Time Slot 2 Talk Group 2 + # This rule is NOT enabled by default + # This rule can be enabled by transmitting on TGID 8 + # This rule can be disabled by transmitting on TGID 9 or 10 + # Repeat the above line for as many rules for this IPSC network as you want. + ] }, - 'CLIENTS': { - 'REPEATER-1': { - 'GROUP_HANGTIME': 5, - 'GROUP_VOICE': [ - {'NAME': 'STATEWIDE', 'ACTIVE': False, 'ON': [8,], 'OFF': [9,10], 'SRC_TS': 1, 'SRC_GROUP': 1, 'DST-TYPE': 'MASTER', 'DST_NET': 'MASTER-1', 'DST_TS': 2, 'DST_GROUP': 2}, - # When DMRD received on this CLIENT, Time Slot 1, Talk Group 1; send to MASTER-1 on Time Slot 2 Talk Group 2 - # This rule is NOT enabled by default - # This rule can be enabled by transmitting on TGID 8 - # This rule can be disabled by transmitting on TGID 9 or 10 - # Repeat the above line for as many rules for this IPSC network as you want. - ] - }, + 'REPEATER-1': { + 'GROUP_HANGTIME': 5, + 'GROUP_VOICE': [ + {'NAME': 'STATEWIDE', 'ACTIVE': False, 'ON': [8,], 'OFF': [9,10], 'SRC_TS': 1, 'SRC_GROUP': 1, 'DST-TYPE': 'MASTER', 'DST_NET': 'MASTER-1', 'DST_TS': 2, 'DST_GROUP': 2}, + # When DMRD received on this CLIENT, Time Slot 1, Talk Group 1; send to MASTER-1 on Time Slot 2 Talk Group 2 + # This rule is NOT enabled by default + # This rule can be enabled by transmitting on TGID 8 + # This rule can be disabled by transmitting on TGID 9 or 10 + # Repeat the above line for as many rules for this IPSC network as you want. + ] }, -} \ No newline at end of file +} + +if __name__ == '__main__': + from pprint import pprint + pprint(RULES) \ No newline at end of file diff --git a/hblink.py b/hblink.py index 0fa25df..2f31bb6 100755 --- a/hblink.py +++ b/hblink.py @@ -47,8 +47,7 @@ __status__ = 'pre-alpha' # Global variables used whether we are a module or __main__ -masters = {} -clients = {} +systems = {} # Change the current directory to the location of the application os.chdir(os.path.dirname(os.path.realpath(sys.argv[0]))) @@ -80,17 +79,16 @@ logger.debug('Logging system started, anything from here on gets logged') def handler(_signal, _frame): logger.info('*** HBLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal)) - for client in clients: - this_client = clients[client] - this_client.send_packet('RPTCL'+CONFIG['CLIENTS'][client]['RADIO_ID']) - logger.info('(%s) De-Registering From the Master', client) - - for master in masters: - this_master = masters[master] - for client in CONFIG['MASTERS'][master]['CLIENTS']: - this_master.send_packet(client, 'MSTCL'+client) - logger.info('(%s) Sending De-Registration to Client: %s', master, CONFIG['MASTERS'][master]['CLIENTS'][client]['RADIO_ID']) - + for system in systems: + this_system = systems[system] + if CONFIG['SYSTEMS'][system]['MODE'] == 'MASTER': + for client in CONFIG['SYSTEMS'][system]['CLIENTS']: + this_system.send_client(client, 'MSTCL'+client) + logger.info('(%s) Sending De-Registration to Client: %s', system, CONFIG['SYSTEMS'][system]['CLIENTS'][client]['RADIO_ID']) + elif CONFIG['SYSTEMS'][system]['MODE'] == 'CLIENT': + this_system.send_master('RPTCL'+CONFIG['SYSTEMS'][system]['RADIO_ID']) + logger.info('(%s) De-Registering From the Master', system) + reactor.stop() # Set signal handers so that we can gracefully exit if need be @@ -162,8 +160,9 @@ class HBMASTER(DatagramProtocol): if len(args) == 1: # Define a few shortcuts to make the rest of the class more readable self._master = args[0] - self._config = CONFIG['MASTERS'][self._master] - self._clients = CONFIG['MASTERS'][self._master]['CLIENTS'] + self._system = self._master + self._config = CONFIG['SYSTEMS'][self._master] + self._clients = CONFIG['SYSTEMS'][self._master]['CLIENTS'] # Configure for AMBE audio export if enabled if self._config['EXPORT_AMBE']: @@ -186,19 +185,23 @@ class HBMASTER(DatagramProtocol): if _this_client['LAST_PING']+CONFIG['GLOBAL']['PING_TIME']*CONFIG['GLOBAL']['MAX_MISSED'] < time(): logger.info('(%s) Client %s has timed out', self._master, _this_client['RADIO_ID']) # Remove any timed out clients from the configuration - del CONFIG['MASTERS'][self._master]['CLIENTS'][client] - + del CONFIG['SYSTEMS'][self._master]['CLIENTS'][client] + def send_clients(self, _packet): for _client in self._clients: - self.send_packet(_client, _packet) + self.send_client(_client, _packet) #logger.debug('(%s) Packet sent to client %s', self._master, self._clients[_client]['RADIO_ID']) - def send_packet(self, _client, _packet): + def send_client(self, _client, _packet): _ip = self._clients[_client]['IP'] _port = self._clients[_client]['PORT'] self.transport.write(_packet, (_ip, _port)) # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! #logger.debug('(%s) TX Packet to %s on port %s: %s', self._clients[_client]['RADIO_ID'], self._clients[_client]['IP'], self._clients[_client]['PORT'], h(_packet)) + + # Alias for other programs to use a common name to send a packet + # regardless of the system type (MASTER or CLIENT) + send_system = send_clients def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _data): pass @@ -229,7 +232,7 @@ class HBMASTER(DatagramProtocol): if self._config['REPEAT'] == True: for _client in self._clients: if _client != _radio_id: - self.send_packet(_client, _data) + self.send_client(_client, _data) logger.debug('(%s) Packet repeated to client: %s', self._master, int_id(_client)) # Userland actions -- typically this is the function you subclass for an application @@ -263,7 +266,7 @@ class HBMASTER(DatagramProtocol): }}) logger.info('(%s) Repeater Logging in with Radio ID: %s, %s:%s', self._master, int_id(_radio_id), _host, _port) _salt_str = hex_str_4(self._clients[_radio_id]['SALT']) - self.send_packet(_radio_id, 'RPTACK'+_salt_str) + self.send_client(_radio_id, 'RPTACK'+_salt_str) self._clients[_radio_id]['CONNECTION'] = 'CHALLENGE_SENT' logger.info('(%s) Sent Challenge Response to %s for login: %s', self._master, int_id(_radio_id), self._clients[_radio_id]['SALT']) else: @@ -283,7 +286,7 @@ class HBMASTER(DatagramProtocol): _calc_hash = a(sha256(_salt_str+self._config['PASSPHRASE']).hexdigest()) if _sent_hash == _calc_hash: _this_client['CONNECTION'] = 'WAITING_CONFIG' - self.send_packet(_radio_id, 'RPTACK'+_radio_id) + self.send_client(_radio_id, 'RPTACK'+_radio_id) logger.info('(%s) Client %s has completed the login exchange successfully', self._master, _this_client['RADIO_ID']) else: logger.info('(%s) Client %s has FAILED the login exchange successfully', self._master, _this_client['RADIO_ID']) @@ -327,7 +330,7 @@ class HBMASTER(DatagramProtocol): _this_client['SOFTWARE_ID'] = _data[224:264] _this_client['PACKAGE_ID'] = _data[264:304] - self.send_packet(_radio_id, 'RPTACK'+_radio_id) + self.send_client(_radio_id, 'RPTACK'+_radio_id) logger.info('(%s) Client %s has sent repeater configuration', self._master, _this_client['RADIO_ID']) else: self.transport.write('MSTNAK'+_radio_id, (_host, _port)) @@ -340,7 +343,7 @@ class HBMASTER(DatagramProtocol): and self._clients[_radio_id]['IP'] == _host \ and self._clients[_radio_id]['PORT'] == _port: self._clients[_radio_id]['LAST_PING'] = time() - self.send_packet(_radio_id, 'MSTPONG'+_radio_id) + self.send_client(_radio_id, 'MSTPONG'+_radio_id) logger.debug('(%s) Received and answered RPTPING from client %s', self._master, int_id(_radio_id)) else: self.transport.write('MSTNAK'+_radio_id, (_host, _port)) @@ -358,7 +361,8 @@ class HBCLIENT(DatagramProtocol): def __init__(self, *args, **kwargs): if len(args) == 1: self._client = args[0] - self._config = CONFIG['CLIENTS'][self._client] + self._system = self._client + self._config = CONFIG['SYSTEMS'][self._client] self._stats = self._config['STATS'] # Configure for AMBE audio export if enabled @@ -381,19 +385,23 @@ class HBCLIENT(DatagramProtocol): self._stats['PINGS_SENT'] = 0 self._stats['PINGS_ACKD'] = 0 self._stats['CONNECTION'] = 'RTPL_SENT' - self.send_packet('RPTL'+self._config['RADIO_ID']) + self.send_master('RPTL'+self._config['RADIO_ID']) logger.info('(%s) Sending login request to master %s:%s', self._client, self._config['MASTER_IP'], self._config['MASTER_PORT']) # If we are connected, sent a ping to the master and increment the counter if self._stats['CONNECTION'] == 'YES': - self.send_packet('RPTPING'+self._config['RADIO_ID']) + self.send_master('RPTPING'+self._config['RADIO_ID']) self._stats['PINGS_SENT'] += 1 logger.debug('(%s) RPTPING Sent to Master. Pings Since Connected: %s', self._client, self._stats['PINGS_SENT']) - def send_packet(self, _packet): + def send_master(self, _packet): self.transport.write(_packet, (self._config['MASTER_IP'], self._config['MASTER_PORT'])) # KEEP THE FOLLOWING COMMENTED OUT UNLESS YOU'RE DEBUGGING DEEPLY!!!! #logger.debug('(%s) TX Packet to %s:%s -- %s', self._client, self._config['MASTER_IP'], self._config['MASTER_PORT'], h(_packet)) + # Alias for other programs to use a common name to send a packet + # regardless of the system type (MASTER or CLIENT) + send_system = send_master + def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _data): pass @@ -433,7 +441,7 @@ class HBCLIENT(DatagramProtocol): logger.info('(%s) Repeater Login ACK Received with 32bit ID: %s', self._client, int_id(_login_int32)) _pass_hash = sha256(_login_int32+self._config['PASSPHRASE']).hexdigest() _pass_hash = a(_pass_hash) - self.send_packet('RPTK'+self._config['RADIO_ID']+_pass_hash) + self.send_master('RPTK'+self._config['RADIO_ID']+_pass_hash) self._stats['CONNECTION'] = 'AUTHENTICATED' elif self._stats['CONNECTION'] == 'AUTHENTICATED': # If we've sent the login challenge... @@ -455,7 +463,7 @@ class HBCLIENT(DatagramProtocol): self._config['SOFTWARE_ID']+\ self._config['PACKAGE_ID'] - self.send_packet('RPTC'+_config_packet) + self.send_master('RPTC'+_config_packet) self._stats['CONNECTION'] = 'CONFIG-SENT' logger.info('(%s) Repeater Configuration Sent', self._client) else: @@ -492,18 +500,14 @@ class HBCLIENT(DatagramProtocol): if __name__ == '__main__': logger.info('HBlink \'HBlink.py\' (c) 2016 N0MJS & the K0USY Group - SYSTEM STARTING...') - # HBlink Master - for master in CONFIG['MASTERS']: - if CONFIG['MASTERS'][master]['ENABLED']: - masters[master] = HBMASTER(master) - reactor.listenUDP(CONFIG['MASTERS'][master]['PORT'], masters[master], interface=CONFIG['MASTERS'][master]['IP']) - logger.debug('MASTER instance created: %s, %s', master, masters[master]) + # HBlink instance creation + for system in CONFIG['SYSTEMS']: + if CONFIG['SYSTEMS'][system]['ENABLED']: + if CONFIG['SYSTEMS'][system]['MODE'] == 'MASTER': + systems[system] = HBMASTER(system) + elif CONFIG['SYSTEMS'][system]['MODE'] == 'CLIENT': + systems[system] = HBCLIENT(system) + reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP']) + logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system]) - # HBlink Client - for client in CONFIG['CLIENTS']: - if CONFIG['CLIENTS'][client]['ENABLED']: - clients[client] = HBCLIENT(client) - reactor.listenUDP(CONFIG['CLIENTS'][client]['PORT'], clients[client], interface=CONFIG['CLIENTS'][client]['IP']) - logger.debug('CLIENT instance created: %s, %s', client, clients[client]) - reactor.run() \ No newline at end of file