2013-08-02 16:31:55 -04:00
# Copyright (c) 2013 Cortney T. Buffington, N0MJS n0mjs@me.com
#
# This work is licensed under the Creative Commons Attribution-ShareAlike
# 3.0 Unported License.To view a copy of this license, visit
# http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to
# Creative Commons, 444 Castro Street, Suite 900, Mountain View,
# California, 94041, USA.
2013-06-27 17:15:54 -04:00
from __future__ import print_function
from twisted . internet . protocol import DatagramProtocol
from twisted . internet import reactor
from twisted . internet import task
2013-10-12 12:08:06 -04:00
import ConfigParser
import os
2013-07-11 20:45:09 -04:00
import sys
2013-06-27 19:17:52 -04:00
import argparse
2013-06-27 17:15:54 -04:00
import binascii
import hmac
import hashlib
2013-07-15 13:04:48 -04:00
import socket
2013-08-06 23:33:04 -04:00
2013-07-11 20:45:09 -04:00
#************************************************
# IMPORTING OTHER FILES - '#include'
#************************************************
# Import system logger configuration
2013-07-20 09:28:52 -04:00
#
2013-07-11 20:45:09 -04:00
try :
2013-09-10 16:28:18 -04:00
from ipsc . ipsc_logger import logger
2013-07-11 20:45:09 -04:00
except ImportError :
2013-10-10 00:07:38 -04:00
sys . exit ( ' System logger configuration not found or invalid ' )
2013-07-11 20:45:09 -04:00
# Import IPSC message types and version information
2013-07-20 09:28:52 -04:00
#
2013-07-11 20:45:09 -04:00
try :
2013-09-10 16:28:18 -04:00
from ipsc . ipsc_message_types import *
2013-07-11 20:45:09 -04:00
except ImportError :
sys . exit ( ' IPSC message types file not found or invalid ' )
# Import IPSC flag mask values
2013-07-20 09:28:52 -04:00
#
2013-07-11 20:45:09 -04:00
try :
2013-09-10 16:28:18 -04:00
from ipsc . ipsc_mask import *
2013-07-11 20:45:09 -04:00
except ImportError :
sys . exit ( ' IPSC mask values file not found or invalid ' )
2013-10-12 12:08:06 -04:00
#************************************************
# PARSE THE CONFIG FILE AND BUILD STRUCTURE
#************************************************
2013-10-12 15:19:52 -04:00
ACTIVE_CALLS = [ ]
2013-10-12 12:08:06 -04:00
NETWORK = { }
config = ConfigParser . ConfigParser ( )
config . read ( ' ./dmrlink.cfg ' )
for section in config . sections ( ) :
NETWORK . update ( { section : { ' LOCAL ' : { } , ' MASTER ' : { } , ' PEERS ' : [ ] } } )
NETWORK [ section ] [ ' LOCAL ' ] . update ( {
' MODE ' : ' ' ,
' PEER_OPER ' : True ,
' PEER_MODE ' : ' DIGITAL ' ,
' FLAGS ' : ' ' ,
' MAX_MISSED ' : 5 ,
' NUM_PEERS ' : 0 ,
' STATUS ' : {
' ACTIVE ' : False
} ,
' ENABLED ' : config . getboolean ( section , ' ENABLED ' ) ,
' TS1_LINK ' : config . getboolean ( section , ' TS1_LINK ' ) ,
' TS2_LINK ' : config . getboolean ( section , ' TS2_LINK ' ) ,
' AUTH_ENABLED ' : config . getboolean ( section , ' AUTH_ENABLED ' ) ,
' RADIO_ID ' : ( config . get ( section , ' RADIO_ID ' ) . rjust ( 8 , ' 0 ' ) ) . decode ( ' hex ' ) ,
' PORT ' : config . getint ( section , ' PORT ' ) ,
' ALIVE_TIMER ' : config . getint ( section , ' ALIVE_TIMER ' ) ,
' AUTH_KEY ' : ( config . get ( section , ' AUTH_KEY ' ) . rjust ( 40 , ' 0 ' ) ) . decode ( ' hex ' ) ,
} )
NETWORK [ section ] [ ' MASTER ' ] . update ( {
' RADIO_ID ' : ' \x00 \x00 \x00 \x00 ' ,
' MODE ' : ' \x00 ' ,
' PEER_OPER ' : False ,
' PEER_MODE ' : ' ' ,
' TS1_LINK ' : False ,
' TS2_LINK ' : False ,
' FLAGS ' : ' \x00 \x00 \x00 \x00 ' ,
' STATUS ' : {
' CONNECTED ' : False ,
' PEER-LIST ' : False ,
' KEEP_ALIVES_SENT ' : 0 ,
' KEEP_ALIVES_MISSED ' : 0 ,
' KEEP_ALIVES_OUTSTANDING ' : 0
} ,
' IP ' : config . get ( section , ' MASTER_IP ' ) ,
' PORT ' : config . getint ( section , ' MASTER_PORT ' )
} )
if NETWORK [ section ] [ ' LOCAL ' ] [ ' AUTH_ENABLED ' ] :
NETWORK [ section ] [ ' LOCAL ' ] [ ' FLAGS ' ] = ' \x00 \x00 \x00 \x14 '
else :
NETWORK [ section ] [ ' LOCAL ' ] [ ' FLAGS ' ] = ' \x00 \x00 \x00 \x04 '
if not NETWORK [ section ] [ ' LOCAL ' ] [ ' TS1_LINK ' ] and not NETWORK [ section ] [ ' LOCAL ' ] [ ' TS2_LINK ' ] :
NETWORK [ section ] [ ' LOCAL ' ] [ ' MODE ' ] = ' \x65 '
elif NETWORK [ section ] [ ' LOCAL ' ] [ ' TS1_LINK ' ] and not NETWORK [ section ] [ ' LOCAL ' ] [ ' TS2_LINK ' ] :
NETWORK [ section ] [ ' LOCAL ' ] [ ' MODE ' ] = ' \x66 '
elif not NETWORK [ section ] [ ' LOCAL ' ] [ ' TS1_LINK ' ] and NETWORK [ section ] [ ' LOCAL ' ] [ ' TS2_LINK ' ] :
NETWORK [ section ] [ ' LOCAL ' ] [ ' MODE ' ] = ' \x69 '
else :
NETWORK [ section ] [ ' LOCAL ' ] [ ' MODE ' ] = ' \x6A '
2013-07-11 20:45:09 -04:00
#************************************************
2013-09-10 16:28:18 -04:00
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
2013-07-11 20:45:09 -04:00
#************************************************
2013-06-29 00:36:39 -04:00
2013-09-10 16:28:18 -04:00
def call_ctl_1 ( ) :
pass
def call_ctl_2 ( ) :
pass
def call_ctl_3 ( ) :
pass
def xcmp_xnl ( ) :
pass
2013-10-12 12:08:06 -04:00
def group_voice ( _network , _data ) :
2013-09-10 16:28:18 -04:00
# _log = logger.debug
2013-10-12 15:19:52 -04:00
_src_sub = int ( binascii . b2a_hex ( _data [ 6 : 9 ] ) , 16 )
_src_group = int ( binascii . b2a_hex ( _data [ 9 : 12 ] ) , 16 )
_src_ipsc = int ( binascii . b2a_hex ( _data [ 1 : 5 ] ) , 16 )
_call = binascii . b2a_hex ( _data [ 17 : 18 ] )
if _call == ' 00 ' :
if ( _network , ' Slot 1 ' ) not in ACTIVE_CALLS :
ACTIVE_CALLS . append ( ( _network , ' Slot 1 ' ) )
print ( ' ( {} ) CALL START Group Voice: IPSC SRC {} , RF SRC: {} , DST Group {} , Slot 1 ' . format ( _network , _src_ipsc , _src_sub , _src_group ) )
if _call == ' 20 ' :
if ( _network , ' Slot 2 ' ) not in ACTIVE_CALLS :
ACTIVE_CALLS . append ( ( _network , ' Slot 2 ' ) )
print ( ' ( {} ) CALL START Group Voice: IPSC SRC {} , RF SRC: {} , DST Group {} , Slot 2 ' . format ( _network , _src_ipsc , _src_sub , _src_group ) )
if _call == ' 40 ' :
ACTIVE_CALLS . remove ( ( _network , ' Slot 1 ' ) )
print ( ' ( {} ) CALL END Group Voice: IPSC SRC {} , RF SRC: {} , DST Group {} , Slot 1 ' . format ( _network , _src_ipsc , _src_sub , _src_group ) )
if _call == ' 60 ' :
ACTIVE_CALLS . remove ( ( _network , ' Slot 2 ' ) )
print ( ' ( {} ) CALL END Group Voice: IPSC SRC {} , RF SRC: {} , DST Group {} , Slot 2 ' . format ( _network , _src_ipsc , _src_sub , _src_group ) )
2013-09-10 16:28:18 -04:00
2013-10-12 12:08:06 -04:00
'''
2013-09-10 16:28:18 -04:00
for source in NETWORK [ _network ] [ ' RULES ' ] [ ' GROUP_VOICE ' ] :
# Matching for rules is against the Destination Group in the SOURCE packet (SRC_GROUP)
if source [ ' SRC_GROUP ' ] == _src_group :
_target = source [ ' DST_NET ' ]
_target_sock = NETWORK [ _target ] [ ' MASTER ' ] [ ' IP ' ] , NETWORK [ _target ] [ ' MASTER ' ] [ ' PORT ' ]
# Re-Write the IPSC SRC to match the target network's ID
_data = _data . replace ( _src_ipsc , NETWORK [ _target ] [ ' LOCAL ' ] [ ' RADIO_ID ' ] )
# Re-Write the destinaion Group ID
_data = _data . replace ( _src_group , source [ ' DST_GROUP ' ] )
# Calculate and append the authentication hash for the target network... if necessary
if NETWORK [ _target ] [ ' LOCAL ' ] [ ' AUTH_KEY ' ] == True :
_data = hashed_packet ( NETWORK [ _target ] [ ' LOCAL ' ] [ ' AUTH_KEY ' ] , _data )
# Send the packet to all peers in the target IPSC
send_to_ipsc ( _target , _data )
2013-10-12 12:08:06 -04:00
'''
2013-09-10 16:28:18 -04:00
def private_voice ( ) :
pass
def group_data ( ) :
pass
def private_data ( ) :
pass
def unknown_message ( ) :
pass
#************************************************
# UTILITY FUNCTIONS FOR INTERNAL USE
#************************************************
2013-07-20 09:28:52 -04:00
2013-07-29 14:38:59 -04:00
# Remove the hash from a paket and return the payload
#
def strip_hash ( _data ) :
2013-08-30 17:23:12 -04:00
# _log = logger.debug
2013-09-10 16:28:18 -04:00
# _log('Stripped Packet: %s', binascii.b2a_hex(_data[:-10]))
2013-07-29 14:38:59 -04:00
return _data [ : - 10 ]
2013-08-06 23:33:04 -04:00
2013-07-29 22:12:12 -04:00
# Determine if the provided peer ID is valid for the provided network
2013-07-29 22:06:25 -04:00
#
2013-08-06 23:33:04 -04:00
def valid_peer ( _peer_list , _peerid ) :
2013-08-30 17:23:12 -04:00
# _log = logger.debug
2013-08-06 23:33:04 -04:00
if _peerid in _peer_list :
2013-09-10 16:28:18 -04:00
# _log('Peer List Has An Entry For: %s', binascii.b2a_hex(_peerid))
2013-08-06 23:33:04 -04:00
return True
2013-09-10 16:28:18 -04:00
# _log('Peer List Does NOT Have An Entry For: %s', binascii.b2a_hex(_peerid))
2013-08-06 23:33:04 -04:00
return False
2013-07-29 14:38:59 -04:00
2013-07-29 22:12:12 -04:00
# Determine if the provided master ID is valid for the provided network
#
def valid_master ( _network , _peerid ) :
2013-08-30 17:23:12 -04:00
# _log = logger.debug
2013-07-30 11:52:10 -04:00
if NETWORK [ _network ] [ ' MASTER ' ] [ ' RADIO_ID ' ] == _peerid :
2013-09-10 16:28:18 -04:00
# _log('Master ID is Valid: %s', binascii.b2a_hex(_peerid))
return True
2013-07-29 14:23:37 -04:00
else :
2013-09-10 16:28:18 -04:00
# _log('Master ID is NOT Valid: %s', binascii.b2a_hex(_peerid))
2013-07-29 14:23:37 -04:00
return False
2013-09-10 16:28:18 -04:00
2013-08-26 09:16:54 -04:00
2013-08-28 12:06:54 -04:00
# Accept a complete packet, ready to be sent, and send it to all active peers + master in an IPSC
#
def send_to_ipsc ( _target , _packet ) :
2013-08-30 17:23:12 -04:00
# _log = logger.debug
# Send to the Master
2013-09-10 16:28:18 -04:00
# _log('Sending %s to:', binascii.b2a_hex(_packet)
2013-08-28 12:06:54 -04:00
networks [ _target ] . transport . write ( _packet , ( NETWORK [ _target ] [ ' MASTER ' ] [ ' IP ' ] , NETWORK [ _target ] [ ' MASTER ' ] [ ' PORT ' ] ) )
2013-09-10 16:28:18 -04:00
# _log(' Master: %s', binascii.b2a_hex(NETWORK[_target]['MASTER']['RADIO_ID']))
2013-08-30 17:23:12 -04:00
# Send to each connected Peer
2013-08-28 12:06:54 -04:00
for peer in NETWORK [ _target ] [ ' PEERS ' ] :
if peer [ ' STATUS ' ] [ ' CONNECTED ' ] == True :
networks [ _target ] . transport . write ( _packet , ( peer [ ' IP ' ] , peer [ ' PORT ' ] ) )
2013-09-10 16:28:18 -04:00
# _log(' Peer: %s', binascii.b2a_hex(peer['RADIO_ID']))
2013-08-28 12:06:54 -04:00
2013-08-30 17:23:12 -04:00
# De-register a peer from an IPSC by removing it's infomation
#
def de_register_peer ( _network , _peerid ) :
# _log = logger.debug
# Iterate for the peer in our data
2013-09-10 16:28:18 -04:00
# _log('Peer De-Registration Requested for: %s', binascii.b2a_hex(_peerid))
2013-08-30 17:23:12 -04:00
for peer in NETWORK [ _network ] [ ' PEERS ' ] :
# If we find the peer, remove it (we should find it)
if _peerid == peer [ ' RADIO_ID ' ] :
NETWORK [ _network ] [ ' PEERS ' ] . remove ( peer )
2013-09-10 16:28:18 -04:00
# _log(' Peer Found And De-Registered')
return
else :
# _log(' Peer NOT Found')
pass
2013-08-30 17:23:12 -04:00
2013-07-29 14:38:59 -04:00
# Take a recieved peer list and the network it belongs to, process and populate the
2013-08-30 17:23:12 -04:00
# data structure in my_ipsc_config with the results, and return a simple list of peers.
2013-07-29 14:38:59 -04:00
#
2013-08-07 15:15:53 -04:00
def process_peer_list ( _data , _network , _peer_list ) :
2013-08-30 17:23:12 -04:00
# _log = logger.debug
# Set the status flag to indicate we have recieved a Peer List
2013-07-28 23:22:04 -04:00
NETWORK [ _network ] [ ' MASTER ' ] [ ' STATUS ' ] [ ' PEER-LIST ' ] = True
2013-09-10 10:43:45 -04:00
# Determine the length of the peer list for the parsing iterator
_peer_list_length = int ( binascii . b2a_hex ( _data [ 5 : 7 ] ) , 16 )
# Record the number of peers in the data structure... we'll use it later (11 bytes per peer entry)
NETWORK [ _network ] [ ' LOCAL ' ] [ ' NUM_PEERS ' ] = _peer_list_length / 11
2013-08-30 17:23:12 -04:00
# _log('<<- (%s) The Peer List has been Received from Master\n%s There are %s peers in this IPSC Network', _network, (' '*(len(_network)+7)), _num_peers)
2013-07-28 23:22:04 -04:00
2013-08-30 17:23:12 -04:00
# Iterate each peer entry in the peer list. Skip the header, then pull the next peer, the next, etc.
2013-09-10 10:43:45 -04:00
for i in range ( 7 , ( _peer_list_length ) + 7 , 11 ) :
2013-08-30 17:23:12 -04:00
# Extract various elements from each entry...
_hex_radio_id = ( _data [ i : i + 4 ] )
_hex_address = ( _data [ i + 4 : i + 8 ] )
_ip_address = socket . inet_ntoa ( _hex_address )
_hex_port = ( _data [ i + 8 : i + 10 ] )
_port = int ( binascii . b2a_hex ( _hex_port ) , 16 )
_hex_mode = ( _data [ i + 10 : i + 11 ] )
_mode = int ( binascii . b2a_hex ( _hex_mode ) , 16 )
# mask individual Mode parameters
_link_op = _mode & PEER_OP_MSK
_link_mode = _mode & PEER_MODE_MSK
_ts1 = _mode & IPSC_TS1_MSK
_ts2 = _mode & IPSC_TS2_MSK
# Determine whether or not the peer is operational
if _link_op == 0b01000000 :
_peer_op = True
else :
_peer_op = False
# Determine the operational mode of the peer
if _link_mode == 0b00000000 :
_peer_mode = ' NO_RADIO '
elif _link_mode == 0b00010000 :
_peer_mode = ' ANALOG '
elif _link_mode == 0b00100000 :
_peer_mode = ' DIGITAL '
else :
_peer_node = ' NO_RADIO '
# Determine whether or not timeslot 1 is linked
if _ts1 == 0b00001000 :
_ts1 = True
else :
_ts1 = False
# Determine whether or not timeslot 2 is linked
if _ts2 == 0b00000010 :
_ts2 = True
else :
_ts2 = False
# If this entry was NOT already in our list, add it.
2013-09-10 16:28:18 -04:00
# Note: We keep a "simple" peer list in addition to the large data
# structure because soemtimes, we just need to identify a
# peer quickly.
2013-08-30 17:23:12 -04:00
if _hex_radio_id not in _peer_list :
_peer_list . append ( _hex_radio_id )
2013-08-06 23:33:04 -04:00
NETWORK [ _network ] [ ' PEERS ' ] . append ( {
2013-08-30 17:23:12 -04:00
' RADIO_ID ' : _hex_radio_id ,
' IP ' : _ip_address ,
' PORT ' : _port ,
' MODE ' : _hex_mode ,
' PEER_OPER ' : _peer_op ,
' PEER_MODE ' : _peer_mode ,
' TS1_LINK ' : _ts1 ,
' TS2_LINK ' : _ts2 ,
2013-08-06 23:33:04 -04:00
' STATUS ' : { ' CONNECTED ' : False , ' KEEP_ALIVES_SENT ' : 0 , ' KEEP_ALIVES_MISSED ' : 0 , ' KEEP_ALIVES_OUTSTANDING ' : 0 }
2013-08-28 12:06:54 -04:00
} )
2013-08-06 23:35:31 -04:00
return _peer_list
2013-08-06 23:33:04 -04:00
2013-07-28 23:22:04 -04:00
2013-07-20 09:28:52 -04:00
# Gratuituous print-out of the peer list.. Pretty much debug stuff.
#
2013-07-28 23:33:14 -04:00
def print_peer_list ( _network ) :
2013-08-30 17:23:12 -04:00
# _log = logger.info
2013-09-10 16:28:18 -04:00
_status = NETWORK [ _network ] [ ' MASTER ' ] [ ' STATUS ' ] [ ' PEER-LIST ' ]
print ( ' Peer List Status for {} : {} ' . format ( _network , _status ) )
if _status and not NETWORK [ _network ] [ ' PEERS ' ] :
print ( ' We are the only peer for: %s ' % _network )
2013-09-10 21:36:35 -04:00
print ( ' ' )
2013-07-28 23:33:14 -04:00
return
2013-09-10 21:36:35 -04:00
2013-07-31 13:33:31 -04:00
print ( ' Peer List for: %s ' % _network )
for dictionary in NETWORK [ _network ] [ ' PEERS ' ] :
2013-07-31 21:56:49 -04:00
if dictionary [ ' RADIO_ID ' ] == NETWORK [ _network ] [ ' LOCAL ' ] [ ' RADIO_ID ' ] :
me = ' (self) '
else :
me = ' '
print ( ' \t RADIO ID: {} {} ' . format ( int ( binascii . b2a_hex ( dictionary [ ' RADIO_ID ' ] ) , 16 ) , me ) )
2013-07-31 13:33:31 -04:00
print ( ' \t \t IP Address: {} : {} ' . format ( dictionary [ ' IP ' ] , dictionary [ ' PORT ' ] ) )
print ( ' \t \t Operational: {} , Mode: {} , TS1 Link: {} , TS2 Link: {} ' . format ( dictionary [ ' PEER_OPER ' ] , dictionary [ ' PEER_MODE ' ] , dictionary [ ' TS1_LINK ' ] , dictionary [ ' TS2_LINK ' ] ) )
2013-07-31 14:58:53 -04:00
print ( ' \t \t Status: {} , KeepAlives Sent: {} , KeepAlives Outstanding: {} , KeepAlives Missed: {} ' . format ( dictionary [ ' STATUS ' ] [ ' CONNECTED ' ] , dictionary [ ' STATUS ' ] [ ' KEEP_ALIVES_SENT ' ] , dictionary [ ' STATUS ' ] [ ' KEEP_ALIVES_OUTSTANDING ' ] , dictionary [ ' STATUS ' ] [ ' KEEP_ALIVES_MISSED ' ] ) )
2013-07-31 13:33:31 -04:00
print ( ' ' )
2013-07-11 20:45:09 -04:00
2013-07-20 09:28:52 -04:00
#************************************************
#******** ***********
2013-07-20 16:41:21 -04:00
#******** IPSC Network 'Engine' ***********
2013-07-20 09:28:52 -04:00
#******** ***********
#************************************************
2013-07-11 20:45:09 -04:00
#************************************************
2013-09-10 16:28:18 -04:00
# Base Class (used nearly all of the time)
2013-07-11 20:45:09 -04:00
#************************************************
2013-06-27 17:15:54 -04:00
2013-09-10 16:28:18 -04:00
2013-06-27 17:15:54 -04:00
class IPSC ( DatagramProtocol ) :
2013-07-20 09:28:52 -04:00
# Modify the initializer to set up our environment and build the packets
# we need to maitain connections
#
2013-06-27 17:15:54 -04:00
def __init__ ( self , * args , * * kwargs ) :
if len ( args ) == 1 :
2013-07-20 16:41:21 -04:00
# Housekeeping: create references to the configuration and status data for this IPSC instance.
2013-07-28 16:53:56 -04:00
# Some configuration objects that are used frequently and have lengthy names are shortened
2013-08-30 17:23:12 -04:00
# such as (self._master_sock) expands to (self._config['MASTER']['IP'], self._config['MASTER']['PORT']).
# Note that many of them reference each other... this is the Pythonic way.
2013-07-20 16:41:21 -04:00
#
2013-07-28 17:22:25 -04:00
self . _network = args [ 0 ]
self . _config = NETWORK [ self . _network ]
2013-07-28 16:53:56 -04:00
#
self . _local = self . _config [ ' LOCAL ' ]
self . _local_stat = self . _local [ ' STATUS ' ]
self . _local_id = self . _local [ ' RADIO_ID ' ]
#
self . _master = self . _config [ ' MASTER ' ]
self . _master_stat = self . _master [ ' STATUS ' ]
self . _master_sock = self . _master [ ' IP ' ] , self . _master [ ' PORT ' ]
#
self . _peers = self . _config [ ' PEERS ' ]
2013-08-06 23:33:04 -04:00
#
2013-08-08 22:48:28 -04:00
# This is a regular list to store peers for the IPSC. At times, parsing a simple list is much less
# Spendy than iterating a list of dictionaries... Maybe I'll find a better way in the future. Also
# We have to know when we have a new peer list, so a variable to indicate we do (or don't)
#
2013-08-06 23:33:04 -04:00
self . _peer_list = [ ]
2013-08-08 22:48:28 -04:00
self . _peer_list_new = False
2013-07-28 16:53:56 -04:00
2013-06-27 17:15:54 -04:00
args = ( )
2013-07-20 16:41:21 -04:00
2013-08-30 17:23:12 -04:00
# Packet 'constructors' - builds the necessary control packets for this IPSC instance.
# This isn't really necessary for anything other than readability (reduction of code golf)
2013-07-20 16:41:21 -04:00
#
2013-07-28 16:53:56 -04:00
self . TS_FLAGS = ( self . _local [ ' MODE ' ] + self . _local [ ' FLAGS ' ] )
self . MASTER_REG_REQ_PKT = ( MASTER_REG_REQ + self . _local_id + self . TS_FLAGS + IPSC_VER )
self . MASTER_ALIVE_PKT = ( MASTER_ALIVE_REQ + self . _local_id + self . TS_FLAGS + IPSC_VER )
self . PEER_LIST_REQ_PKT = ( PEER_LIST_REQ + self . _local_id )
self . PEER_REG_REQ_PKT = ( PEER_REG_REQ + self . _local_id + IPSC_VER )
self . PEER_REG_REPLY_PKT = ( PEER_REG_REPLY + self . _local_id + IPSC_VER )
self . PEER_ALIVE_REQ_PKT = ( PEER_ALIVE_REQ + self . _local_id + self . TS_FLAGS )
self . PEER_ALIVE_REPLY_PKT = ( PEER_ALIVE_REPLY + self . _local_id + self . TS_FLAGS )
2013-10-12 12:08:06 -04:00
2013-06-27 17:15:54 -04:00
else :
2013-07-20 16:41:21 -04:00
# If we didn't get called correctly, log it!
#
2013-07-28 17:22:25 -04:00
logger . error ( ' ( %s ) Unexpected arguments found. ' , self . _network )
2013-09-10 16:28:18 -04:00
2013-07-20 09:28:52 -04:00
# This is called by REACTOR when it starts, We use it to set up the timed
# loop for each instance of the IPSC engine
#
2013-06-27 17:15:54 -04:00
def startProtocol ( self ) :
2013-07-20 16:41:21 -04:00
# Timed loop for IPSC connection establishment and maintenance
2013-09-10 16:28:18 -04:00
# Others could be added later for things like updating a status
# Web page, etc....
2013-07-20 16:41:21 -04:00
#
2013-07-15 13:04:48 -04:00
self . _call = task . LoopingCall ( self . timed_loop )
2013-07-28 16:53:56 -04:00
self . _loop = self . _call . start ( self . _local [ ' ALIVE_TIMER ' ] )
2013-06-27 17:15:54 -04:00
2013-07-20 09:28:52 -04:00
2013-09-10 16:28:18 -04:00
# Take a packet to be SENT, calcualte auth hash and return the whole thing
#
def hashed_packet ( self , _key , _data ) :
# _log = logger.debug
_hash = binascii . a2b_hex ( ( hmac . new ( _key , _data , hashlib . sha1 ) ) . hexdigest ( ) [ : 20 ] )
# _log('Hash for: %s is %s', binascii.b2a_hex(_data), binascii.b2a_hex(_hash)
return ( _data + _hash )
# Take a RECEIVED packet, calculate the auth hash and verify authenticity
#
def validate_auth ( self , _key , _data ) :
# _log = logger.debug
_payload = strip_hash ( _data )
_hash = _data [ - 10 : ]
_chk_hash = binascii . a2b_hex ( ( hmac . new ( _key , _payload , hashlib . sha1 ) ) . hexdigest ( ) [ : 20 ] )
if _chk_hash == _hash :
# _log(' AUTH: Valid - Payload: %s, Hash: %s', binascii.b2a_hex(_payload), binascii.b2a_hex(_hash))
return True
else :
# _log(' AUTH: Invalid - Payload: %s, Hash: %s', binascii.b2a_hex(_payload), binascii.b2a_hex(_hash))
return False
2013-07-20 09:28:52 -04:00
#************************************************
# TIMED LOOP - MY CONNECTION MAINTENANCE
#************************************************
2013-08-30 17:23:12 -04:00
def timed_loop ( self ) :
# Right now, without this, we really dont' know anything is happening.
2013-10-12 15:19:52 -04:00
# print_peer_list(self._network)
2013-09-10 16:28:18 -04:00
2013-08-30 17:23:12 -04:00
# If the master isn't connected, we have to do that before we can do anything else!
2013-09-10 16:28:18 -04:00
if self . _master_stat [ ' CONNECTED ' ] == False :
reg_packet = self . hashed_packet ( self . _local [ ' AUTH_KEY ' ] , self . MASTER_REG_REQ_PKT )
2013-07-28 16:53:56 -04:00
self . transport . write ( reg_packet , ( self . _master_sock ) )
2013-07-20 09:28:52 -04:00
2013-08-30 17:23:12 -04:00
# Once the master is connected, we have to send keep-alives.. and make sure we get them back
2013-08-08 22:48:28 -04:00
elif ( self . _master_stat [ ' CONNECTED ' ] == True ) :
2013-08-30 17:23:12 -04:00
# Send keep-alive to the master
2013-09-10 16:28:18 -04:00
master_alive_packet = self . hashed_packet ( self . _local [ ' AUTH_KEY ' ] , self . MASTER_ALIVE_PKT )
2013-07-28 16:53:56 -04:00
self . transport . write ( master_alive_packet , ( self . _master_sock ) )
2013-07-20 09:28:52 -04:00
2013-08-30 17:23:12 -04:00
# If we had a keep-alive outstanding by the time we send another, mark it missed.
2013-07-28 16:53:56 -04:00
if ( self . _master_stat [ ' KEEP_ALIVES_OUTSTANDING ' ] ) > 0 :
self . _master_stat [ ' KEEP_ALIVES_MISSED ' ] + = 1
2013-07-20 09:28:52 -04:00
2013-08-30 17:23:12 -04:00
# If we have missed too many keep-alives, de-regiseter the master and start over.
2013-07-28 16:53:56 -04:00
if self . _master_stat [ ' KEEP_ALIVES_OUTSTANDING ' ] > = self . _local [ ' MAX_MISSED ' ] :
self . _master_stat [ ' CONNECTED ' ] = False
2013-07-20 09:28:52 -04:00
logger . error ( ' Maximum Master Keep-Alives Missed -- De-registering the Master ' )
2013-08-30 17:23:12 -04:00
# Update our stats before we move on...
2013-08-01 16:09:20 -04:00
self . _master_stat [ ' KEEP_ALIVES_SENT ' ] + = 1
self . _master_stat [ ' KEEP_ALIVES_OUTSTANDING ' ] + = 1
2013-07-20 09:28:52 -04:00
else :
2013-08-30 17:23:12 -04:00
# This is bad. If we get this message, probably need to restart the program.
2013-07-28 17:22:25 -04:00
logger . error ( ' ->> ( %s ) Master in UNKOWN STATE: %s : %s ' , self . _network , self . _master_sock )
2013-08-30 17:23:12 -04:00
# If the master is connected and we don't have a peer-list yet....
if ( ( self . _master_stat [ ' CONNECTED ' ] == True ) and ( self . _master_stat [ ' PEER-LIST ' ] == False ) ) :
# Ask the master for a peer-list
2013-09-10 16:28:18 -04:00
peer_list_req_packet = self . hashed_packet ( self . _local [ ' AUTH_KEY ' ] , self . PEER_LIST_REQ_PKT )
2013-07-28 16:53:56 -04:00
self . transport . write ( peer_list_req_packet , ( self . _master_sock ) )
2013-07-20 09:28:52 -04:00
2013-08-30 17:23:12 -04:00
# If we do ahve a peer-list, we need to register with the peers and send keep-alives...
2013-08-08 22:48:28 -04:00
if ( self . _master_stat [ ' PEER-LIST ' ] == True ) :
2013-08-30 17:23:12 -04:00
# Iterate the list of peers... so we do this for each one.
2013-07-28 16:53:56 -04:00
for peer in ( self . _peers ) :
2013-08-30 17:23:12 -04:00
# We will show up in the peer list, but shouldn't try to talk to ourselves.
if ( peer [ ' RADIO_ID ' ] == self . _local_id ) :
2013-07-20 09:28:52 -04:00
continue
2013-08-30 17:23:12 -04:00
# If we haven't registered to a peer, send a registration
2013-07-26 18:29:47 -04:00
if peer [ ' STATUS ' ] [ ' CONNECTED ' ] == False :
2013-09-10 16:28:18 -04:00
peer_reg_packet = self . hashed_packet ( self . _local [ ' AUTH_KEY ' ] , self . PEER_REG_REQ_PKT )
2013-07-20 09:28:52 -04:00
self . transport . write ( peer_reg_packet , ( peer [ ' IP ' ] , peer [ ' PORT ' ] ) )
2013-08-30 17:23:12 -04:00
# If we have registered with the peer, then send a keep-alive
2013-07-26 18:29:47 -04:00
elif peer [ ' STATUS ' ] [ ' CONNECTED ' ] == True :
2013-09-10 16:28:18 -04:00
peer_alive_req_packet = self . hashed_packet ( self . _local [ ' AUTH_KEY ' ] , self . PEER_ALIVE_REQ_PKT )
2013-07-20 16:41:21 -04:00
self . transport . write ( peer_alive_req_packet , ( peer [ ' IP ' ] , peer [ ' PORT ' ] ) )
2013-08-30 17:23:12 -04:00
# If we have a keep-alive outstanding by the time we send another, mark it missed.
2013-07-26 18:29:47 -04:00
if peer [ ' STATUS ' ] [ ' KEEP_ALIVES_OUTSTANDING ' ] > 0 :
peer [ ' STATUS ' ] [ ' KEEP_ALIVES_MISSED ' ] + = 1
2013-08-30 17:23:12 -04:00
# If we have missed too many keep-alives, de-register the peer and start over.
2013-07-28 16:53:56 -04:00
if peer [ ' STATUS ' ] [ ' KEEP_ALIVES_OUTSTANDING ' ] > = self . _local [ ' MAX_MISSED ' ] :
2013-07-26 18:29:47 -04:00
peer [ ' STATUS ' ] [ ' CONNECTED ' ] = False
2013-08-08 22:48:28 -04:00
self . _peer_list . remove ( peer [ ' RADIO_ID ' ] ) # Remove the peer from the simple list FIRST
self . _peers . remove ( peer ) # Becuase once it's out of the dictionary, you can't use it for anything else.
2013-08-07 15:15:53 -04:00
logger . error ( ' Maximum Peer Keep-Alives Missed -- De-registering the Peer: %s ' , peer )
2013-08-01 16:09:20 -04:00
2013-08-30 17:23:12 -04:00
# Update our stats before moving on...
2013-08-01 16:09:20 -04:00
peer [ ' STATUS ' ] [ ' KEEP_ALIVES_SENT ' ] + = 1
peer [ ' STATUS ' ] [ ' KEEP_ALIVES_OUTSTANDING ' ] + = 1
2013-07-20 09:28:52 -04:00
2013-09-14 20:07:32 -04:00
def _notify_event ( self , network , event , info ) :
"""
Used internally whenever an event happens that may be useful to notify the outside world about .
Arguments :
network : string , network name to look up in config
event : string , basic description
info : dict , in the interest of accomplishing as much as possible without code changes .
The dict will typically contain a peer_id so the origin of the event is known .
"""
pass
2013-07-15 13:04:48 -04:00
2013-07-20 09:28:52 -04:00
#************************************************
# RECEIVED DATAGRAM - ACT IMMEDIATELY!!!
#************************************************
2013-08-30 17:23:12 -04:00
# Actions for recieved packets by type: For every packet recieved, there are some things that we need to do:
# Decode some of the info
# Check for auth and authenticate the packet
# Strip the hash from the end... we don't need it anymore
#
# Once they're done, we move on to the proccessing or callbacks for each packet type.
2013-07-20 09:28:52 -04:00
#
2013-06-27 17:15:54 -04:00
def datagramReceived ( self , data , ( host , port ) ) :
2013-07-15 13:04:48 -04:00
_packettype = data [ 0 : 1 ]
_peerid = data [ 1 : 5 ]
2013-07-28 17:22:25 -04:00
_dec_peerid = int ( binascii . b2a_hex ( _peerid ) , 16 )
2013-07-29 14:23:37 -04:00
2013-08-08 22:48:28 -04:00
# First action: if Authentication is active, authenticate the packet
#
2013-07-31 13:33:31 -04:00
if bool ( self . _local [ ' AUTH_KEY ' ] ) == True :
2013-08-30 17:23:12 -04:00
# Validate
2013-09-10 16:28:18 -04:00
if self . validate_auth ( self . _local [ ' AUTH_KEY ' ] , data ) == False :
2013-07-31 13:33:31 -04:00
logger . warning ( ' ( %s ) AuthError: IPSC packet failed authentication. Type %s : Peer ID: %s ' , self . _network , binascii . b2a_hex ( _packettype ) , _dec_peerid )
return
2013-08-30 17:23:12 -04:00
# Strip the hash, we won't need it anymore
2013-08-08 22:48:28 -04:00
data = strip_hash ( data )
2013-06-29 00:36:39 -04:00
2013-08-08 22:48:28 -04:00
# Packets generated by "users" that are the most common should come first for efficiency.
#
2013-07-31 21:46:03 -04:00
if ( _packettype == GROUP_VOICE ) :
2013-08-30 17:23:12 -04:00
# Don't take action unless it's from a valid peer (including the master, of course)
2013-08-06 23:33:04 -04:00
if not ( valid_master ( self . _network , _peerid ) == False or valid_peer ( self . _peer_list , _peerid ) == False ) :
2013-07-31 21:46:03 -04:00
logger . warning ( ' ( %s ) PeerError: Peer not in peer-list: %s ' , self . _network , _dec_peerid )
return
2013-09-14 20:07:32 -04:00
self . _notify_event ( self . _network , ' group_voice ' , { ' peer_id ' : _dec_peerid } )
2013-09-10 16:28:18 -04:00
group_voice ( self . _network , data )
2013-07-31 21:46:03 -04:00
2013-08-08 22:48:28 -04:00
# IPSC keep alives, master and peer, come next in processing priority
#
2013-07-31 21:46:03 -04:00
elif ( _packettype == PEER_ALIVE_REQ ) :
2013-08-30 17:23:12 -04:00
# We should not answer a keep-alive request from a peer we don't know about!
2013-08-06 23:33:04 -04:00
if valid_peer ( self . _peer_list , _peerid ) == False :
logger . warning ( ' ( %s ) PeerError: Peer %s not in peer-list: %s ' , self . _network , _dec_peerid , self . _peer_list )
2013-07-30 12:50:29 -04:00
return
2013-08-30 17:23:12 -04:00
# Generate a hashed paket from our template and send it.
2013-09-10 16:28:18 -04:00
peer_alive_reply_packet = self . hashed_packet ( self . _local [ ' AUTH_KEY ' ] , self . PEER_ALIVE_REPLY_PKT )
2013-09-14 20:07:32 -04:00
self . _notify_event ( self . _network , ' peer_keepalive ' , { ' peer_id ' : _dec_peerid } )
2013-07-11 20:45:09 -04:00
self . transport . write ( peer_alive_reply_packet , ( host , port ) )
elif ( _packettype == MASTER_ALIVE_REPLY ) :
2013-08-30 17:23:12 -04:00
# We should not accept keep-alive reply from someone claming to be a master who isn't!
2013-07-30 12:50:29 -04:00
if valid_master ( self . _network , _peerid ) == False :
2013-08-06 23:33:04 -04:00
logger . warning ( ' ( %s ) PeerError: Peer %s not in peer-list: %s ' , self . _network , _dec_peerid , self . _peer_list )
2013-07-30 12:50:29 -04:00
return
2013-08-30 17:23:12 -04:00
# logger.debug('<<- (%s) Master Keep-alive Reply From: %s \t@ IP: %s:%s', self._network, _dec_peerid, host, port)
# This action is so simple, it doesn't require a callback function, master is responding, we're good.
2013-08-01 16:09:20 -04:00
self . _master_stat [ ' KEEP_ALIVES_OUTSTANDING ' ] = 0
2013-07-11 20:45:09 -04:00
elif ( _packettype == PEER_ALIVE_REPLY ) :
2013-08-30 17:23:12 -04:00
# Find the peer in our list of peers...
2013-08-01 16:09:20 -04:00
for peer in self . _config [ ' PEERS ' ] :
if peer [ ' RADIO_ID ' ] == _peerid :
2013-08-30 17:23:12 -04:00
# No callback funcntion needed, set the outstanding keepalives to 0, and move on.
2013-08-01 16:09:20 -04:00
peer [ ' STATUS ' ] [ ' KEEP_ALIVES_OUTSTANDING ' ] = 0
2013-08-08 22:48:28 -04:00
# Registration requests and replies are infrequent, but important. Peer lists can go here too as a part
# of the registration process.
#
2013-07-11 20:45:09 -04:00
elif ( _packettype == MASTER_REG_REQ ) :
2013-08-30 17:23:12 -04:00
# We can't operate as a master as of now, so we should never receive one of these.
# logger.debug('<<- (%s) Master Registration Packet Recieved', self._network)
pass
2013-06-29 00:36:39 -04:00
2013-08-30 17:23:12 -04:00
# When we hear from the maseter, record it's ID, flag that we're connected, and reset the dead counter.
2013-06-27 17:15:54 -04:00
elif ( _packettype == MASTER_REG_REPLY ) :
2013-07-30 11:52:10 -04:00
self . _master [ ' RADIO_ID ' ] = _peerid
2013-07-28 16:53:56 -04:00
self . _master_stat [ ' CONNECTED ' ] = True
self . _master_stat [ ' KEEP_ALIVES_OUTSTANDING ' ] = 0
2013-08-30 17:23:12 -04:00
# Answer a peer registration request -- simple, no callback runction needed
2013-07-15 13:04:48 -04:00
elif ( _packettype == PEER_REG_REQ ) :
2013-10-10 17:23:52 -04:00
if valid_peer ( self . _peer_list , _peerid ) :
peer_reg_reply_packet = self . hashed_packet ( self . _local [ ' AUTH_KEY ' ] , self . PEER_REG_REPLY_PKT )
self . transport . write ( peer_reg_reply_packet , ( host , port ) )
self . _notify_event ( self . _network , ' peer_registration ' , { ' peer_id ' : _dec_peerid } )
2013-07-15 13:04:48 -04:00
elif ( _packettype == PEER_REG_REPLY ) :
2013-09-14 20:07:32 -04:00
self . _notify_event ( self . _network , ' peer_registration_reply ' , { ' peer_id ' : _dec_peerid } )
2013-07-26 18:29:47 -04:00
for peer in self . _config [ ' PEERS ' ] :
if peer [ ' RADIO_ID ' ] == _peerid :
peer [ ' STATUS ' ] [ ' CONNECTED ' ] = True
2013-06-29 00:36:39 -04:00
2013-06-27 17:15:54 -04:00
elif ( _packettype == PEER_LIST_REPLY ) :
2013-09-10 16:28:18 -04:00
if len ( data ) > 18 :
self . _peer_list = process_peer_list ( data , self . _network , self . _peer_list )
else :
NETWORK [ self . _network ] [ ' MASTER ' ] [ ' STATUS ' ] [ ' PEER-LIST ' ] = True
2013-07-11 20:45:09 -04:00
elif ( _packettype == DE_REG_REQ ) :
2013-08-30 17:23:12 -04:00
de_register_peer ( self . _network , _peerid )
2013-08-27 11:32:27 -04:00
logger . warning ( ' <<- ( %s ) Peer De-Registration Request From: %s : %s ' , self . _network , host , port )
2013-07-11 20:45:09 -04:00
elif ( _packettype == DE_REG_REPLY ) :
2013-08-27 11:32:27 -04:00
logger . warning ( ' <<- ( %s ) Peer De-Registration Reply From: %s : %s ' , self . _network , host , port )
2013-07-11 20:45:09 -04:00
2013-08-08 22:48:28 -04:00
elif ( _packettype == RPT_WAKE_UP ) :
2013-08-27 11:32:27 -04:00
logger . warning ( ' <<- ( %s ) Repeater Wake-Up Packet From: %s : %s ' , self . _network , host , port )
2013-08-08 22:48:28 -04:00
2013-09-10 16:28:18 -04:00
# Other "user" related packet types that we don't do much or anything with yet
2013-08-08 22:48:28 -04:00
#
2013-09-10 16:28:18 -04:00
elif ( _packettype == PVT_VOICE ) :
private_voice ( )
elif ( _packettype == GROUP_DATA ) :
group_data ( )
elif ( _packettype == PVT_DATA ) :
private_data ( )
elif ( _packettype == XCMP_XNL ) : # NOTE: We currently indicate we are not XCMP/XNL capable!
xcmp_xnl ( )
2013-08-08 22:48:28 -04:00
2013-09-10 16:28:18 -04:00
elif ( _packettype == CALL_CTL_1 ) :
call_control_1 ( )
elif ( _packettype == CALL_CTL_2 ) :
call_control_2 ( )
elif ( _packettype == CALL_CTL_3 ) :
call_control_3 ( )
2013-07-11 20:45:09 -04:00
2013-08-08 22:48:28 -04:00
# If there's a packet type we don't know aobut, it should be logged so we can figure it out and take an appropriate action!
2013-06-27 17:15:54 -04:00
else :
2013-09-10 16:28:18 -04:00
unknown_message ( _packettype , data )
2013-06-27 17:15:54 -04:00
2013-09-10 16:28:18 -04:00
#************************************************
# Derived Class
# used in the rare event of an
# unauthenticated IPSC network.
#************************************************
2013-07-20 09:28:52 -04:00
2013-09-10 16:28:18 -04:00
class UnauthIPSC ( IPSC ) :
# There isn't a hash to build, so just return the data
#
def hashed_packet ( self , _key , _data ) :
return ( _data )
# Everything is validated, so just return True
#
def validate_auth ( self , _key , _data ) :
return True
2013-07-20 09:28:52 -04:00
2013-07-11 20:45:09 -04:00
#************************************************
# MAIN PROGRAM LOOP STARTS HERE
#************************************************
2013-06-27 17:15:54 -04:00
if __name__ == ' __main__ ' :
2013-08-16 15:30:20 -04:00
networks = { }
2013-07-11 20:45:09 -04:00
for ipsc_network in NETWORK :
2013-10-12 12:08:06 -04:00
if ( NETWORK [ ipsc_network ] [ ' LOCAL ' ] [ ' ENABLED ' ] ) :
2013-09-10 21:36:35 -04:00
if NETWORK [ ipsc_network ] [ ' LOCAL ' ] [ ' AUTH_ENABLED ' ] == True :
networks [ ipsc_network ] = IPSC ( ipsc_network )
else :
networks [ ipsc_network ] = UnauthIPSC ( ipsc_network )
2013-08-16 15:30:20 -04:00
reactor . listenUDP ( NETWORK [ ipsc_network ] [ ' LOCAL ' ] [ ' PORT ' ] , networks [ ipsc_network ] )
2013-10-10 00:07:38 -04:00
reactor . run ( )