Merge pull request #22 from n0mjs710/socket-reporting

Socket reporting
This commit is contained in:
Cort Buffington 2017-05-01 16:46:56 -05:00 committed by GitHub
commit d5ff0ff0bf
7 changed files with 4314 additions and 637 deletions

View File

@ -44,18 +44,20 @@
# Use to make test strings: #print('PKT:', "\\x".join("{:02x}".format(ord(c)) for c in _data))
from __future__ import print_function
from twisted.internet.protocol import Factory, Protocol
from twisted.protocols.basic import NetstringReceiver
from twisted.internet import reactor
from twisted.internet import task
from binascii import b2a_hex as ahex
from time import time
from importlib import import_module
from cPickle import dump as pickle_dump
import cPickle as pickle
import sys
from dmr_utils.utils import hex_str_3, hex_str_4, int_id
from dmrlink import IPSC, systems, config_reports
from dmrlink import IPSC, systems, config_reports, hmac_new, sha1
from ipsc.ipsc_const import BURST_DATA_TYPE
@ -139,7 +141,39 @@ def build_acl(_sub_acl):
return ACL
# Timed loop used for reporting IPSC status
#
# REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE
def config_reports(_config):
if _config['REPORTS']['REPORT_NETWORKS'] == 'PICKLE':
def reporting_loop(_logger):
_logger.debug('Periodic Reporting Loop Started (PICKLE)')
try:
with open(_config['REPORTS']['REPORT_PATH']+'dmrlink_stats.pickle', 'wb') as file:
pickle.dump(_config['SYSTEMS'], file, 2)
file.close()
except IOError as detail:
_logger.error('I/O Error: %s', detail)
elif _config['REPORTS']['REPORT_NETWORKS'] == 'PRINT':
def reporting_loop(_logger):
_logger.debug('Periodic Reporting Loop Started (PRINT)')
for system in _config['SYSTEMS']:
print_master(_config, system)
print_peer_list(_config, system)
elif _config['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
def reporting_loop(_logger, _server):
_logger.debug('Periodic Reporting Loop Started (NETWORK)')
_server.send_config()
_server.send_bridge()
else:
def reporting_loop(_logger):
_logger.debug('Periodic Reporting Loop Started (NULL)')
return reporting_loop
# Run this every minute for rule timer updates
def rule_timer_loop():
logger.info('(ALL IPSC SYSTEMS) Rule timer loop started')
@ -170,13 +204,15 @@ def rule_timer_loop():
else:
logger.debug('Conference Bridge NO ACTION: System: %s, Bridge: %s, TS: %s, TGID: %s', _system['SYSTEM'], _bridge, _system['TS'], int_id(_system['TGID']))
if BRIDGE_CONF['REPORT']:
if BRIDGE_CONF['REPORT'] == 'pickle':
try:
with open(CONFIG['REPORTS']['REPORT_PATH']+'confbridge_stats.pickle', 'wb') as file:
pickle_dump(BRIDGES, file, 2)
file.close()
except IOError as detail:
_logger.error('I/O Error: %s', detail)
elif BRIDGE_CONF['REPORT'] == 'network':
report_server.send_clients('bridge updated')
class confbridgeIPSC(IPSC):
@ -367,6 +403,56 @@ class confbridgeIPSC(IPSC):
#
#
# Socket-based reporting section
#
class report(NetstringReceiver):
def __init__(self):
pass
def connectionMade(self):
report_server.clients.append(self)
logger.info('DMRlink reporting client connected: %s', self.transport.getPeer())
def connectionLost(self, reason):
logger.info('DMRlink reporting client disconnected: %s', self.transport.getPeer())
report_server.clients.remove(self)
def stringReceived(self, data):
self.process_message(data)
def process_message(self, _message):
opcode = _message[:1]
if opcode == REP_OPC['CONFIG_REQ']:
logger.info('DMRlink reporting client sent \'CONFIG_REQ\': %s', self.transport.getPeer())
self.send_config()
else:
print('got unknown opcode')
class reportFactory(Factory):
def __init__(self):
pass
def buildProtocol(self, addr):
if (addr.host) in CONFIG['REPORTS']['REPORT_CLIENTS']:
return report()
else:
return None
def send_clients(self, _message):
for client in report_server.clients:
client.sendString(_message)
def send_config(self):
serialized = pickle.dumps(CONFIG['SYSTEMS'], protocol=pickle.HIGHEST_PROTOCOL)
self.send_clients(REP_OPC['CONFIG_SND']+serialized)
def send_bridge(self):
serialized = pickle.dumps(BRIDGES, protocol=pickle.HIGHEST_PROTOCOL)
self.send_clients(REP_OPC['BRIDGE_SND']+serialized)
if __name__ == '__main__':
import argparse
import os
@ -448,21 +534,34 @@ if __name__ == '__main__':
# Build the Access Control List
ACL = build_acl('sub_acl')
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = confbridgeIPSC(system, CONFIG, logger)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
if CONFIG['REPORTS']['REPORT_NETWORKS']:
if CONFIG['REPORTS']['REPORT_NETWORKS'] == 'PRINT' or CONFIG['REPORTS']['REPORT_NETWORKS'] == 'PICKLE':
reporting_loop = config_reports(CONFIG)
reporting = task.LoopingCall(reporting_loop, logger)
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
rule_timer = task.LoopingCall(rule_timer_loop)
rule_timer.start(60)
# INITIALIZE THE NETWORK-BASED REPORTING SERVER
if CONFIG['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
logger.info('(confbridge.py) TCP reporting server starting')
from ipsc.reporting_const import REPORT_OPCODES as REP_OPC
report_server = reportFactory()
report_server.clients = []
reactor.listenTCP(CONFIG['REPORTS']['REPORT_PORT'], reportFactory())
reporting_loop = config_reports(CONFIG)
reporting = task.LoopingCall(reporting_loop, logger, report_server)
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
rule_timer = task.LoopingCall(rule_timer_loop)
rule_timer.start(60)
reactor.run()

View File

@ -33,6 +33,8 @@ import os
import logging
import signal
import cPickle as pickle
from logging.config import dictConfig
from hmac import new as hmac_new
from binascii import b2a_hex as ahex
@ -41,11 +43,10 @@ from hashlib import sha1
from socket import inet_ntoa as IPAddr
from socket import inet_aton as IPHexStr
from time import time
from cPickle import dump as pickle_dump
from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor
from twisted.internet import task
from twisted.internet.protocol import DatagramProtocol, Factory, Protocol
from twisted.protocols.basic import NetstringReceiver
from twisted.internet import reactor, task
from ipsc.ipsc_const import *
from ipsc.ipsc_mask import *
@ -74,7 +75,7 @@ def config_reports(_config):
_logger.debug('Periodic Reporting Loop Started (PICKLE)')
try:
with open(_config['REPORTS']['REPORT_PATH']+'dmrlink_stats.pickle', 'wb') as file:
pickle_dump(_config['SYSTEMS'], file, 2)
pickle.dump(_config['SYSTEMS'], file, 2)
file.close()
except IOError as detail:
_logger.error('I/O Error: %s', detail)
@ -85,6 +86,11 @@ def config_reports(_config):
for system in _config['SYSTEMS']:
print_master(_config, system)
print_peer_list(_config, system)
elif _config['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
def reporting_loop(_logger, _server):
_logger.debug('Periodic Reporting Loop Started (NETWORK)')
_server.send_config()
else:
def reporting_loop(_logger):
@ -162,6 +168,7 @@ def build_peer_list(_peers):
concatenated_peers += peer + hex_ip + hex_port + mode
peer_list = hex_str_2(len(concatenated_peers)) + concatenated_peers
return peer_list
# Gratuitous print-out of the peer list.. Pretty much debug stuff.
@ -926,7 +933,51 @@ class IPSC(DatagramProtocol):
self.unknown_message(_packettype, _peerid, data)
return
#
# Socket-based reporting section
#
class report(NetstringReceiver):
def __init__(self):
pass
def connectionMade(self):
report_server.clients.append(self)
logger.info('DMRlink reporting client connected: %s', self.transport.getPeer())
def connectionLost(self, reason):
logger.info('DMRlink reporting client disconnected: %s', self.transport.getPeer())
report_server.clients.remove(self)
def stringReceived(self, data):
self.process_message(data)
def process_message(self, _message):
opcode = _message[:1]
if opcode == REP_OPC['CONFIG_REQ']:
logger.info('DMRlink reporting client sent \'CONFIG_REQ\': %s', self.transport.getPeer())
self.send_config()
else:
print('got unknown opcode')
class reportFactory(Factory):
def __init__(self):
pass
def buildProtocol(self, addr):
if (addr.host) in CONFIG['REPORTS']['REPORT_CLIENTS']:
return report()
else:
return None
def send_clients(self, _message):
for client in report_server.clients:
client.sendString(_message)
def send_config(self):
serialized = pickle.dumps(CONFIG['SYSTEMS'], protocol=pickle.HIGHEST_PROTOCOL)
self.send_clients(REP_OPC['CONFIG_SND']+serialized)
#************************************************
# MAIN PROGRAM LOOP STARTS HERE
@ -984,9 +1035,22 @@ if __name__ == '__main__':
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
if CONFIG['REPORTS']['REPORT_NETWORKS']:
if CONFIG['REPORTS']['REPORT_NETWORKS'] == 'PRINT' or CONFIG['REPORTS']['REPORT_NETWORKS'] == 'PICKLE':
reporting_loop = config_reports(CONFIG)
reporting = task.LoopingCall(reporting_loop, logger)
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
# INITIALIZE THE NETWORK-BASED REPORTING SERVER
elif CONFIG['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
logger.info('(confbridge.py) TCP reporting server starting')
from ipsc.reporting_const import REPORT_OPCODES as REP_OPC
report_server = reportFactory()
report_server.clients = []
reactor.listenTCP(CONFIG['REPORTS']['REPORT_PORT'], reportFactory())
reporting_loop = config_reports(CONFIG)
reporting = task.LoopingCall(reporting_loop, logger, report_server)
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
reactor.run()

View File

@ -19,24 +19,34 @@ PATH: /opt/dmrlink/
# specifiec by "REPORT_INTERVAL" in seconds. Possible values
# for "REPORT_NETWORKS" are:
# PICKLE - a Python pickle file of the network's data structure
# (JSON DOES NOT WORK RNIGHT NOW) JSON - a JSON file of the network's data structure
# (REDIS DOES NOT WORK RIGHT NOW) REDIS - send JSON format data structure to a local|remote
# redis server
#
# PRINT - a pretty print (STDOUT) of the data structure
# "PRINT_PEERS_INC_MODE" - Boolean to include mode bits
# "PRINT_PEERS_INC_FLAGS" - Boolean to include flag bits
#
# NETWORK - This is the right way to do it. Opens a TCP socket
# listener. The protocol is still in its infancy, but the
# idea is that dmrlink will talk to another application
# to send event and status updates. Of course, the big
# goal here is a web dashboard that doesn't live on the
# dmrlink machine itself.
#
# PRINT is the odd man out because it sends prettily formatted stuff
# to STDOUT. The others send the internal data structure of the IPSC
# instance and let some program on the other end sort it out.
#
# REPORT_INTERVAL - Seconds between reports
# REPORT_PATH - Absolute path save data (pickle and json)
# REPORT_PORT - TCP port to listen on if "REPORT_NETWORKS" = NETWORK
# REPORT_CLIENTS - comma separated list of IPs you will allow clients
# to connect on.
#
[REPORTS]
REPORT_NETWORKS:
REPORT_INTERVAL: 60
REPORT_PATH:
REPORT_PORT: 4321
REPORT_CLIENTS: 127.0.0.1, 192.168.1.1
PRINT_PEERS_INC_MODE: 0
PRINT_PEERS_INC_FLAGS: 0

View File

@ -56,9 +56,13 @@ def build_config(_config_file):
'REPORT_NETWORKS': config.get(section, 'REPORT_NETWORKS'),
'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'),
'REPORT_PATH': config.get(section, 'REPORT_PATH'),
'REPORT_PORT': config.get(section, 'REPORT_PORT'),
'REPORT_CLIENTS': config.get(section, 'REPORT_CLIENTS').split(','),
'PRINT_PEERS_INC_MODE': config.getboolean(section, 'PRINT_PEERS_INC_MODE'),
'PRINT_PEERS_INC_FLAGS': config.getboolean(section, 'PRINT_PEERS_INC_FLAGS')
})
if CONFIG['REPORTS']['REPORT_PORT']:
CONFIG['REPORTS']['REPORT_PORT'] = int(CONFIG['REPORTS']['REPORT_PORT'])
elif section == 'LOGGER':
CONFIG['LOGGER'].update({

30
ipsc/reporting_const.py Normal file
View File

@ -0,0 +1,30 @@
###############################################################################
# Copyright (C) 201t Cortney T. Buffington, N0MJS <n0mjs@me.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
###############################################################################
# Opcodes for the network-based reporting protocol
REPORT_OPCODES = {
'CONFIG_REQ': '\x00',
'CONFIG_SND': '\x01',
'BRIDGE_REQ': '\x02',
'BRIDGE_SND': '\x03',
'CONFIG_UPD': '\x04',
'BRIDGE_UPD': '\x05',
'LINK_EVENT': '\x06',
'BRDG_EVENT': '\x07'
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff