Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba1d108d06 | |||
| 56c66dba5a | |||
| d6c22c4721 | |||
| ee96c6752e | |||
| 0be40df13b | |||
| 47a2b7db13 | |||
| e223b26a99 | |||
| b5ce0edbae | |||
| 68d52ee976 | |||
| 8cbf6c678f | |||
| bbe299fc60 | |||
| 8d451abebc | |||
| f915131a23 | |||
| 77e446c1f1 | |||
| 9835c3f317 | |||
| 134a34f22e | |||
| 9accbafe68 | |||
| 10c5c0c1ac | |||
| 79ab1adde5 | |||
| bba5d42511 | |||
| 182fe4f93b | |||
| 71b35032a0 | |||
| a03c6d9789 | |||
| 70127c021f | |||
| 4cbabc48aa | |||
| 80114833b1 | |||
| 8cad9ee839 | |||
| 6ab3fa7bc3 | |||
| a4a339aabf | |||
| 43e11ea19a | |||
| 10012548e9 | |||
| 851711bb7e | |||
| 59d4058c84 | |||
| 21c68c2186 | |||
| ff9469aaea | |||
| 3ffa45f5e8 | |||
| 45455322ce | |||
| 39aa714907 | |||
| c3a33c7c85 | |||
| 3e8177f80b | |||
| dc07204f03 | |||
| 31ecf3b733 | |||
| dbe69bb15e | |||
| 620d013e92 | |||
| f25f97a045 | |||
| 6197240725 | |||
| 98300901cc | |||
| 874b11db7b | |||
| cd217b94b5 |
@@ -5,4 +5,7 @@ Icon
|
||||
dmrlink.cfg
|
||||
pub*
|
||||
bridge_rules.py
|
||||
playback_config.py
|
||||
*.pyc
|
||||
*.bak
|
||||
*.lcl
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
Message Types: (1st byte in the payload)
|
||||
|
||||
|
||||
REGISTRATION EXCHANGE
|
||||
---------------------
|
||||
PEER:
|
||||
90 00 c8 32 68 65 00 00 e0 3c 04 03 04 02 0f 9b 2d d4 d3 af 36 da c2 fe
|
||||
|--SRC ID-| |MODE| | FLAGS | |IPSC VER| |IPSC VER| |-1st 10 bytes of SHA-1 Hash-|
|
||||
13120104
|
||||
|
||||
MASTER:
|
||||
91 00 04 c2 c0 6a 00 00 80 5d 00 03 04 03 04 00 5c b8 4e e4 7e 44 b6 bb df dd
|
||||
|--SRC ID-| |MODE| | FLAGS ||PEERS||IPSC VER| |IPSC VER| |-1st 10 bytes of SHA-1 Hash-|
|
||||
312000
|
||||
|
||||
----
|
||||
PEER:
|
||||
96 00 c8 32 68 65 00 00 e0 3c 04 03 04 02 45 d0 a9 c9 07 5c 05 ad 50 67
|
||||
|--SRC ID-| |MODE| | FLAGS | |IPSC VER| |IPSC VER| |-1st 10 bytes of SHA-1 Hash-|
|
||||
13120104
|
||||
|
||||
MASTER:
|
||||
97 00 04 c2 c0 6a 00 00 80 5d 04 03 04 02 32 c0 f5 d4 02 28 f5 13 48 22
|
||||
|--SRC ID-| |MODE| | FLAGS | |IPSC VER| |IPSC VER| |-1st 10 bytes of SHA-1 Hash-|
|
||||
312000
|
||||
|
||||
----
|
||||
PEER:
|
||||
92 00 c8 32 68 85 26 37 9b 06 93 9a af dd 08
|
||||
|--SRC ID-| |-1st 10 bytes of SHA-1 Hash-|
|
||||
|
||||
|
||||
MASTER: (This appears to be the peer list)
|
||||
93 00 04 c2 c0 00 37 00 04 c2 c3 d1 72 71 e9 c3 5a 6a 00 04 c2 c4 6c c8 a4 3d c3 50 6a 00 04 c2 c5 44 67 16 bb c3 5c 6a 00 c8 32 65 a4 71 4c 0c c3 51 6a ...SHA-1 truncated
|
||||
|TPE| |--SRC ID-| |-LEN-| |-PEER ID-| |-PEER IP-| |PRT||MDE| |-PEER ID-| |-PEER IP-| |PRT||MDE| |-PEER ID-| |-PEER IP-| |PRT||MDE| |-PEER ID-| |-PEER IP-| |PRT||MDE|
|
||||
13120104 44 (4) 3120103 209.114.113.233 50010 1&2 3120104 108.200.164.61 50000 1&2 3120105 68.103.22.187 50012 1&2 13120101 164.113.26.12 50001 1&2
|
||||
|
||||
|
||||
------------------------------------------------------------
|
||||
Copyright (c) 2013 Cortney T. Buffington, N0MJS and the K0USY Group. 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.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2013 Cortney T. Buffington, N0MJS and the K0USY Group. n0mjs@me.com
|
||||
Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group. 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
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
***OFFICIAL VERSION V0.1 RELEASE***
|
||||
***OFFICIAL VERSION V0.2 RELEASE***
|
||||
|
||||
|
||||
##PROJECT: Open Source IPSC Client.
|
||||
**PURPOSE:** Troubleshooting IPSC performance issues, and create applications such as logging, bridging, etc..
|
||||
**IMPACT:** Potential concern from Motorla Solutions, as IPSC is a proprietary
|
||||
**METHOD:** Reverse engineering by pattern matching and process of elimination
|
||||
**PURPOSE:** Understanding IPSC, building an open-source IPSC "stack", troubleshooting IPSC performance issues, and as a basis to easily write applications such as logging, bridging, etc.
|
||||
|
||||
**IMPACT:** Potential concern from Motorla Solutions, as IPSC is a proprietary protocol.
|
||||
|
||||
**METHOD:** Reverse engineering by pattern matching and process of elimination.
|
||||
|
||||
**PROPERTY:**
|
||||
This work represents the author's interpretation of the Motorola(tm) MOTOTRBO(tm) IPSC protocol. It is intended for academic purposes and not for commercial gain. It is not guaranteed to work, or be useful in any way, though it is intended to help IPSC users better understand, and thus maintain and operate, IPSC networks. This work is not affiliated with Motorola Solutions(tm), Inc. in any way. Motorola, Motorola Solutions, MOTOTRBO, ISPC and other terms in this document are registered trademarks of Motorola Solutions, Inc. Other registered trademark terms may be used. These are owned and held by their respective owners.
|
||||
@@ -13,7 +15,24 @@ This work represents the author's interpretation of the Motorola(tm) MOTOTRBO(tm
|
||||
This document assumes the reader is familiar with the concepts presented in the Motorola Solutions(tm), Inc. MOTOTRBO(tm) Systems Planner.
|
||||
|
||||
**CONVENTIONS USED:**
|
||||
When communications exchanges are described, the symbols "->" and "<-" are used to denote the *direction* of the communcation. For example, "PEER -> MASTER" indicates communcation from the peer to the master. For each exchange outlined, the initiator of the particular communication will be on the left for the duration of the particular item being illustrated.
|
||||
When communications exchanges are described, the symbols "->" and "<-" are used to denote the *direction* of the communcation. For example, "PEER -> MASTER" indicates communcation from the peer to the master. For each exchange outlined, the initiator of the particular communication will be on the left for the duration of the particular item being illustrated.
|
||||
|
||||
**HOW TO USE THIS SOFTWARE:**
|
||||
The primary objective is the IPSC "stack" itself, and is represended in dmrlink.py. It gets the majority of work, and the applicaitons are examples to show how dmrlink.py can be used. As such, dmrlink.py, dmrlink.cfg and the ipsc directory are pre-requisites for eveyrthing here. dmrlink.py does very little on it's own, but you should ALWAYS make sure it runs directly, as nothing else will work if it does not. Turn on the logging options, set the logger to DEBUG and watch to make sure everything works right **BEFORE** working with the application files.
|
||||
|
||||
dmrlink, optionally, uses three additional files to map dmr identifiers to understandable names. These are the .csv files for subscribers, peers (other repeaters, 3rd party console applications, etc.) and one for common talkgroups. These files are really only meaningful for logging and reporting.
|
||||
|
||||
The remaining files are sample applicaitons that sub-class dmrlink. Since dmrlink takes a default action on each packet type, overriding the class methods for particular packet types are how the examples are presented. For example, bridge.py only needs access to group voice packets, so the group_voice class method is overridden to perform the bridging function. In this particlar example, several other class methods are also overridden, but only to set them to do nothing.
|
||||
|
||||
**FILES:**
|
||||
+ ***dmrlink.py, dmrlink.cfg, ipsc (directory):*** Core files for dmrlink to work
|
||||
+ ***talkgroup_ids.csv, subscriber_ids.csv, peer_ids.csv:*** DMR numeric ID to name mapping files (optional)
|
||||
+ ***bridge.py, log.py, rcm.py, playback.py:*** Sample applications to demonstrate dmrlink's abilities
|
||||
+ ***files with SAMPLE in the name:*** Configuration files for certain apps - remove "_SAMPLE" and customize to your needs to use. for example, "dmrlink_SAMPLE.cfg" becomes "dmrlink.cfg"
|
||||
|
||||
**CONFIGURATION:**
|
||||
|
||||
The configuration file for dmrlink is in ".ini" format, and is self-documented. A warning not in the self-documentation: Don't enable features you do not undertand, it can break dmrlink or the target IPSC (nothing turning off dmrlink shouldn't fix). There are options avaialble because the IPSC protocol appears to make them available, but dmrlink doesn't yet understand them. For exmaple, dmrlink does not process XNL/XCMP. If you enable it, and other peers expect interaction with it, the results may be unpredictable. Chances are, you'll confuse applications like RDAC that require it.
|
||||
|
||||
###CONNECTION ESTABLISHMENT AND MAINTENANCE
|
||||
|
||||
@@ -228,14 +247,11 @@ Number of peers can be derived from PEER_LIST_LENGTH, as each peer entry is 11 b
|
||||
|
||||
**NOTE:**
|
||||
|
||||
This is important: If you e-mail me asking about dmrlink and don't use the phrase "here I am, rock you like a hurricane" (at least initially), I will probably delete your e-mail without reading it. I need to be very clear. This software is NOT intended to be an out-of-box replacement for c-Bridge, SmartPTT, GenWatch, RDAC, etc. Please do not contact me with the express intent of wanting to know how to configure it to do the same thing as any of these fine products, because it doesn't do what they do, and will likely never be something you can "just run" and peform those functions with. This is free software, shared with the world so that others can learn from or do useful things with it. The price of open source is that I didn't sell you a product, and there is no support or warranty, or even responsiblity on my part for your use of it. If you want something that is a c-Bridge or SmartPTT, then please go buy one of those products -- they work great, I own both. Using dmrlink will require you to get your hands dirty. Using dmrlink requires basic understanding of Python. If you have read this README.md, have looked for comments or other direction within the files themsleves, and understand I owe you nothing, then please e-mail me, and I'll try to help.
|
||||
|
||||
Yes, this note probably sounds heavy handed. I have received a great deal of e-mail from a great deal of folks since posting this project. They have ranged from folks helping with development by fixing problems for me or offering the right way to do something where I clearly did it wrong (which I GREATLY APPRECIATE), all the way down to a growing number of people who seem to want to know things such as, "how do you expect me to use this in place of a c-Bridge?". The answer is, I don't. And I need to minimize those e-mails so that I have time to concentrate on making dmrlink a better piece of software.
|
||||
This is important: If you e-mail me asking about dmrlink and don't use the phrase "here I am, rock you like a hurricane" (at least initially), I will probably delete your e-mail without reading it. I need to be very clear. This software is NOT intended to be an out-of-box replacement for c-Bridge, SmartPTT, GenWatch, RDAC, etc. Please do not contact me with the express intent of wanting to know how to configure it to do the same thing as any of these fine products, because it doesn't do what they do, and will likely never be something you can "just run" and peform those functions with. This is free software, shared with the world so that others can learn from or do useful things with it. The price of open source is that I didn't sell you a product, and there is no support or warranty, or even responsiblity on my part for your use of it. If you want something that is a c-Bridge or SmartPTT, then please go buy one of those products -- they work great, I own both. Using dmrlink will require you to get your hands dirty. Using dmrlink requires basic understanding of Python. If you have read this README.md, have looked for comments or other direction within the files themsleves, and understand I owe you nothing, then please e-mail me, and I'll try to help if I can.
|
||||
|
||||
***73 DE N0MJS***
|
||||
|
||||
|
||||
Copyright (c) 2013 Cortney T. Buffington, N0MJS and the K0USY Group. n0mjs@me.com
|
||||
Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group. 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2013 Cortney T. Buffington, N0MJS and the K0USY Group. n0mjs@me.com
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This work is licensed under the Creative Commons Attribution-ShareAlike
|
||||
# 3.0 Unported License.To view a copy of this license, visit
|
||||
@@ -15,6 +15,15 @@ from binascii import b2a_hex as h
|
||||
import sys
|
||||
from dmrlink import IPSC, NETWORK, networks, send_to_ipsc, dmr_nat, logger
|
||||
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
__credits__ = 'Adam Fast, KC0YLK, Dave K, and he who wishes not to be named'
|
||||
__license__ = 'Creative Commons Attribution-ShareAlike 3.0 Unported'
|
||||
__version__ = '0.2a'
|
||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||
__email__ = 'n0mjs@me.com'
|
||||
__status__ = 'Production'
|
||||
|
||||
NAT = 0
|
||||
#NAT = '\x2f\x9b\x80'
|
||||
|
||||
@@ -40,7 +49,7 @@ class bridgeIPSC(IPSC):
|
||||
#************************************************
|
||||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||
#************************************************
|
||||
|
||||
#
|
||||
def group_voice(self, _network, _src_sub, _dst_group, _ts, _end, _peerid, _data):
|
||||
if _ts not in self.ACTIVE_CALLS:
|
||||
self.ACTIVE_CALLS.append(_ts)
|
||||
@@ -70,51 +79,10 @@ class bridgeIPSC(IPSC):
|
||||
# Send the packet to all peers in the target IPSC
|
||||
send_to_ipsc(_target, _tmp_data)
|
||||
|
||||
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
pass
|
||||
|
||||
def group_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
pass
|
||||
|
||||
def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
pass
|
||||
|
||||
def call_mon_origin(self, _network, _data):
|
||||
pass
|
||||
|
||||
def call_mon_rpt(self, _network, _data):
|
||||
pass
|
||||
|
||||
def call_mon_nack(self, _network, _data):
|
||||
pass
|
||||
|
||||
def xcmp_xnl(self, _network, _data):
|
||||
pass
|
||||
|
||||
class bridgeUnauthIPSC(bridgeIPSC):
|
||||
|
||||
# There isn't a hash to build, so just return the data
|
||||
#
|
||||
def hashed_packet(self, _key, _data):
|
||||
return _data
|
||||
|
||||
# Remove the hash from a packet and return the payload... except don't
|
||||
#
|
||||
def strip_hash(self, _data):
|
||||
return _data
|
||||
|
||||
# Everything is validated, so just return True
|
||||
#
|
||||
def validate_auth(self, _key, _data):
|
||||
return True
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.info('DMRlink \'bridge.py\' (c) 2013 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
logger.info('DMRlink \'bridge.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
for ipsc_network in NETWORK:
|
||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
||||
if NETWORK[ipsc_network]['LOCAL']['AUTH_ENABLED']:
|
||||
networks[ipsc_network] = bridgeIPSC(ipsc_network)
|
||||
else:
|
||||
networks[ipsc_network] = bridgeUnauthIPSC(ipsc_network)
|
||||
networks[ipsc_network] = bridgeIPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network])
|
||||
reactor.run()
|
||||
reactor.run()
|
||||
@@ -14,10 +14,14 @@ THIS EXAMPLE WILL NOT WORK AS IT IS - YOU MUST SPECIFY NAMES AND GROUP IDS!!!
|
||||
NOTE: Timeslot transcoding does not yet work (SRC_TS) and (DST_TS) are ignored
|
||||
'''
|
||||
|
||||
def id(_id):
|
||||
# Create a 3 byte TGID or UID from an integer
|
||||
return hex(_id)[2:].rjust(6,'0').decode('hex')
|
||||
|
||||
RULES = {
|
||||
'IPSC_FOO': {
|
||||
'GROUP_VOICE': [
|
||||
{'SRC_GROUP': b'\x00\x00\x01', 'SRC_TS': 1, 'DST_NET': 'IPSC_BAR', 'DST_GROUP': b'\x00\x00\x02', 'DST_TS': 1},
|
||||
{'SRC_GROUP': id(1), 'SRC_TS': 1, 'DST_NET': 'IPSC_BAR', 'DST_GROUP': id(2), 'DST_TS': 1},
|
||||
# Repeat the above line for as many rules for this IPSC network as you want.
|
||||
],
|
||||
'PRIVATE_VOICE': [
|
||||
@@ -29,7 +33,7 @@ RULES = {
|
||||
},
|
||||
'IPSC_BAR:' {
|
||||
'GROUP_VOICE': [
|
||||
{'SRC_GROUP': b'\x00\x00\x02', 'SRC_TS': 1, 'DST_NET': 'IPSC_FOO', 'DST_GROUP': b'\x00\x00\x01', 'DST_TS': 1},
|
||||
{'SRC_GROUP': id(2), 'SRC_TS': 1, 'DST_NET': 'IPSC_FOO', 'DST_GROUP': id(1), 'DST_TS': 1},
|
||||
# Repeat the above line for as many rules for this IPSC network as you want.
|
||||
],
|
||||
'PRIVATE_VOICE': [
|
||||
|
||||
+394
-175
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2013 Cortney T. Buffington, N0MJS and the K0USY Group. n0mjs@me.com
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This work is licensed under the Creative Commons Attribution-ShareAlike
|
||||
# 3.0 Unported License.To view a copy of this license, visit
|
||||
@@ -6,33 +6,49 @@
|
||||
# Creative Commons, 444 Castro Street, Suite 900, Mountain View,
|
||||
# California, 94041, USA.
|
||||
|
||||
#NOTE: This program uses a configuration file specified on the command line
|
||||
# if none is specified, then dmrlink.cfg in the same directory as this
|
||||
# file will be tried. Finally, if that does not exist, this process
|
||||
# will terminate
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import ConfigParser
|
||||
import argparse
|
||||
import sys
|
||||
import binascii
|
||||
import csv
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
import signal
|
||||
|
||||
from logging.config import dictConfig
|
||||
from hmac import new as hmac_new
|
||||
from binascii import b2a_hex as h
|
||||
from hashlib import sha1
|
||||
from socket import inet_ntoa as IPAddr
|
||||
from socket import inet_aton as IPHexStr
|
||||
from twisted.internet.protocol import DatagramProtocol
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet import task
|
||||
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2013 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
__credits__ = 'Adam Fast, KC0YLK, Dave K, and he who wishes not to be named'
|
||||
__license__ = 'Creative Commons Attribution-ShareAlike 3.0 Unported'
|
||||
__version__ = '0.1'
|
||||
__version__ = '0.3'
|
||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||
__email__ = 'n0mjs@me.com'
|
||||
__status__ = 'Production'
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||
|
||||
cli_args = parser.parse_args()
|
||||
|
||||
|
||||
#************************************************
|
||||
# PARSE THE CONFIG FILE AND BUILD STRUCTURE
|
||||
#************************************************
|
||||
@@ -41,10 +57,13 @@ NETWORK = {}
|
||||
networks = {}
|
||||
config = ConfigParser.ConfigParser()
|
||||
|
||||
if not cli_args.CFG_FILE:
|
||||
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||
try:
|
||||
config.read('./dmrlink.cfg')
|
||||
except:
|
||||
sys.exit('Could not open configuration file, exiting...')
|
||||
if not config.read(cli_args.CFG_FILE):
|
||||
sys.exit('Configuration file \''+cli_args.CFG_FILE+'\' is not a valid configuration file! Exiting...')
|
||||
except:
|
||||
sys.exit('Configuration file \''+cli_args.CFG_FILE+'\' is not a valid configuration file! Exiting...')
|
||||
|
||||
try:
|
||||
for section in config.sections():
|
||||
@@ -111,14 +130,21 @@ try:
|
||||
'FLAGS': '\x00\x00\x00\x00',
|
||||
'FLAGS_DECODE': '',
|
||||
'STATUS': {
|
||||
'CONNECTED': False,
|
||||
'PEER_LIST': False,
|
||||
'KEEP_ALIVES_SENT': 0,
|
||||
'KEEP_ALIVES_MISSED': 0,
|
||||
'KEEP_ALIVES_OUTSTANDING': 0
|
||||
'CONNECTED': False,
|
||||
'PEER_LIST': False,
|
||||
'KEEP_ALIVES_SENT': 0,
|
||||
'KEEP_ALIVES_MISSED': 0,
|
||||
'KEEP_ALIVES_OUTSTANDING': 0,
|
||||
'KEEP_ALIVES_RECEIVED': 0,
|
||||
'KEEP_ALIVE_RX_TIME': 0
|
||||
},
|
||||
'IP': config.get(section, 'MASTER_IP'),
|
||||
'PORT': config.getint(section, 'MASTER_PORT')
|
||||
'IP': '',
|
||||
'PORT': ''
|
||||
})
|
||||
if not NETWORK[section]['LOCAL']['MASTER_PEER']:
|
||||
NETWORK[section]['MASTER'].update({
|
||||
'IP': config.get(section, 'MASTER_IP'),
|
||||
'PORT': config.getint(section, 'MASTER_PORT')
|
||||
})
|
||||
|
||||
# Temporary locations for building MODE and FLAG data
|
||||
@@ -168,6 +194,7 @@ try:
|
||||
except:
|
||||
sys.exit('Could not parse configuration file, exiting...')
|
||||
|
||||
|
||||
#************************************************
|
||||
# CONFIGURE THE SYSTEM LOGGER
|
||||
#************************************************
|
||||
@@ -222,6 +249,7 @@ dictConfig({
|
||||
})
|
||||
logger = logging.getLogger('dmrlink')
|
||||
|
||||
|
||||
#************************************************
|
||||
# IMPORTING OTHER FILES - '#include'
|
||||
#************************************************
|
||||
@@ -277,6 +305,30 @@ except ImportError:
|
||||
# UTILITY FUNCTIONS FOR INTERNAL USE
|
||||
#************************************************
|
||||
|
||||
# Create a 2 byte hex string from an integer
|
||||
#
|
||||
def hex_str_2(_int_id):
|
||||
try:
|
||||
return hex(_int_id)[2:].rjust(4,'0').decode('hex')
|
||||
except TypeError:
|
||||
logger.error('hex_str_2: invalid integer length')
|
||||
|
||||
# Create a 3 byte hex string from an integer
|
||||
#
|
||||
def hex_str_3(_int_id):
|
||||
try:
|
||||
return hex(_int_id)[2:].rjust(6,'0').decode('hex')
|
||||
except TypeError:
|
||||
logger.error('hex_str_3: invalid integer length')
|
||||
|
||||
# Create a 4 byte hex string from an integer
|
||||
#
|
||||
def hex_str_4(_int_id):
|
||||
try:
|
||||
return hex(_int_id)[2:].rjust(8,'0').decode('hex')
|
||||
except TypeError:
|
||||
logger.error('hex_str_4: invalid integer length')
|
||||
|
||||
# Convert a hex string to an int (radio ID, etc.)
|
||||
#
|
||||
def int_id(_hex_string):
|
||||
@@ -292,7 +344,7 @@ def dmr_nat(_data, _src_id, _nat_id):
|
||||
#
|
||||
def get_info(_id, _dict):
|
||||
if _id in _dict:
|
||||
return _dict[_id]
|
||||
return _dict[_id]
|
||||
return _id
|
||||
|
||||
# Determine if the provided peer ID is valid for the provided network
|
||||
@@ -320,7 +372,8 @@ def send_to_ipsc(_target, _packet):
|
||||
_peers = _network['PEERS']
|
||||
|
||||
# Send to the Master
|
||||
_network_instance.transport.write(_packet, (_network['MASTER']['IP'], _network['MASTER']['PORT']))
|
||||
if _network['MASTER']['STATUS']['CONNECTED']:
|
||||
_network_instance.transport.write(_packet, (_network['MASTER']['IP'], _network['MASTER']['PORT']))
|
||||
# Send to each connected Peer
|
||||
for peer in _peers.keys():
|
||||
if _peers[peer]['STATUS']['CONNECTED']:
|
||||
@@ -399,7 +452,7 @@ def process_flags_bytes(_hex_flags):
|
||||
'VOICE': _voice,
|
||||
'MASTER': _master
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Take a received peer list and the network it belongs to, process and populate the
|
||||
# data structure in my_ipsc_config with the results, and return a simple list of peers.
|
||||
@@ -411,7 +464,7 @@ def process_peer_list(_data, _network):
|
||||
_peer_list_length = int(h(_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
|
||||
logger.info('(%s) Peer List Received from Master: %s peers in this IPSC', _network, _peer_list_length/11)
|
||||
logger.info('(%s) Peer List Received from Master: %s peers in this IPSC', _network, NETWORK[_network]['LOCAL']['NUM_PEERS'])
|
||||
|
||||
# Iterate each peer entry in the peer list. Skip the header, then pull the next peer, the next, etc.
|
||||
for i in range(7, _peer_list_length +7, 11):
|
||||
@@ -442,18 +495,32 @@ def process_peer_list(_data, _network):
|
||||
'CONNECTED': False,
|
||||
'KEEP_ALIVES_SENT': 0,
|
||||
'KEEP_ALIVES_MISSED': 0,
|
||||
'KEEP_ALIVES_OUTSTANDING': 0
|
||||
'KEEP_ALIVES_OUTSTANDING': 0,
|
||||
'KEEP_ALIVES_RECEIVED': 0,
|
||||
'KEEP_ALIVE_RX_TIME': 0
|
||||
}
|
||||
}
|
||||
logger.debug('(%s) Peer Added: %s', _network, NETWORK[_network]['PEERS'][_hex_radio_id])
|
||||
|
||||
# Finally, check to see if there's a peer already in our list that was not in this peer list
|
||||
# and if so, delete it.
|
||||
for peerid in NETWORK[_network]['PEERS'].keys():
|
||||
if peerid not in _temp_peers:
|
||||
de_register_peer(_network, peerid)
|
||||
logger.warning('(%s) Peer Deleted (not in new peer list): %s', _network, h(peerid))
|
||||
for peer in NETWORK[_network]['PEERS'].keys():
|
||||
if peer not in _temp_peers:
|
||||
de_register_peer(_network, peer)
|
||||
logger.warning('(%s) Peer Deleted (not in new peer list): %s', _network, h(peer))
|
||||
|
||||
# Build a peer list - used when a peer registers, re-regiseters or times out
|
||||
#
|
||||
def build_peer_list(_peers):
|
||||
concatenated_peers = ''
|
||||
for peer in _peers:
|
||||
hex_ip = IPHexStr(_peers[peer]['IP'])
|
||||
hex_port = hex_str_2(_peers[peer]['PORT'])
|
||||
mode = _peers[peer]['MODE']
|
||||
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.
|
||||
#
|
||||
@@ -489,25 +556,42 @@ def print_peer_list(_network):
|
||||
for name, value in _this_peer['FLAGS_DECODE'].items():
|
||||
print('\t\t\t{}: {}' .format(name, value))
|
||||
print('\t\tStatus: {}, KeepAlives Sent: {}, KeepAlives Outstanding: {}, KeepAlives Missed: {}' .format(_this_peer_stat['CONNECTED'], _this_peer_stat['KEEP_ALIVES_SENT'], _this_peer_stat['KEEP_ALIVES_OUTSTANDING'], _this_peer_stat['KEEP_ALIVES_MISSED']))
|
||||
|
||||
print('\t\t KeepAlives Received: {}, Last KeepAlive Received at: {}' .format(_this_peer_stat['KEEP_ALIVES_RECEIVED'], _this_peer_stat['KEEP_ALIVE_RX_TIME']))
|
||||
|
||||
print('')
|
||||
|
||||
# Gratuitous print-out of Master info.. Pretty much debug stuff.
|
||||
#
|
||||
def print_master(_network):
|
||||
_master = NETWORK[_network]['MASTER']
|
||||
print('Master for %s' % _network)
|
||||
print('\tRADIO ID: {}' .format(int(h(_master['RADIO_ID']), 16)))
|
||||
if _master['MODE_DECODE'] and REPORTS['PEER_REPORT_INC_MODE']:
|
||||
print('\t\tMode Values:')
|
||||
for name, value in _master['MODE_DECODE'].items():
|
||||
print('\t\t\t{}: {}' .format(name, value))
|
||||
if _master['FLAGS_DECODE'] and REPORTS['PEER_REPORT_INC_FLAGS']:
|
||||
print('\t\tService Flags:')
|
||||
for name, value in _master['FLAGS_DECODE'].items():
|
||||
print('\t\t\t{}: {}' .format(name, value))
|
||||
print('\t\tStatus: {}, KeepAlives Sent: {}, KeepAlives Outstanding: {}, KeepAlives Missed: {}' .format(_master['STATUS']['CONNECTED'], _master['STATUS']['KEEP_ALIVES_SENT'], _master['STATUS']['KEEP_ALIVES_OUTSTANDING'], _master['STATUS']['KEEP_ALIVES_MISSED']))
|
||||
if NETWORK[_network]['LOCAL']['MASTER_PEER']:
|
||||
print('DMRlink is the Master for %s' % _network)
|
||||
else:
|
||||
_master = NETWORK[_network]['MASTER']
|
||||
print('Master for %s' % _network)
|
||||
print('\tRADIO ID: {}' .format(int(h(_master['RADIO_ID']), 16)))
|
||||
if _master['MODE_DECODE'] and REPORTS['PEER_REPORT_INC_MODE']:
|
||||
print('\t\tMode Values:')
|
||||
for name, value in _master['MODE_DECODE'].items():
|
||||
print('\t\t\t{}: {}' .format(name, value))
|
||||
if _master['FLAGS_DECODE'] and REPORTS['PEER_REPORT_INC_FLAGS']:
|
||||
print('\t\tService Flags:')
|
||||
for name, value in _master['FLAGS_DECODE'].items():
|
||||
print('\t\t\t{}: {}' .format(name, value))
|
||||
print('\t\tStatus: {}, KeepAlives Sent: {}, KeepAlives Outstanding: {}, KeepAlives Missed: {}' .format(_master['STATUS']['CONNECTED'], _master['STATUS']['KEEP_ALIVES_SENT'], _master['STATUS']['KEEP_ALIVES_OUTSTANDING'], _master['STATUS']['KEEP_ALIVES_MISSED']))
|
||||
print('\t\t KeepAlives Received: {}, Last KeepAlive Received at: {}' .format(_master['STATUS']['KEEP_ALIVES_RECEIVED'], _master['STATUS']['KEEP_ALIVE_RX_TIME']))
|
||||
|
||||
# Shut ourselves down gracefully with the IPSC peers.
|
||||
#
|
||||
def handler(_signal, _frame):
|
||||
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||
|
||||
for network in networks:
|
||||
this_ipsc = networks[network]
|
||||
logger.info('De-Registering from IPSC %s', network)
|
||||
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||
send_to_ipsc(network, de_reg_req_pkt)
|
||||
|
||||
reactor.stop()
|
||||
|
||||
#************************************************
|
||||
#******** ***********
|
||||
@@ -522,6 +606,10 @@ def print_master(_network):
|
||||
|
||||
class IPSC(DatagramProtocol):
|
||||
|
||||
#************************************************
|
||||
# IPSC INSTANCE INSTANTIATION
|
||||
#************************************************
|
||||
|
||||
# Modify the initializer to set up our environment and build the packets
|
||||
# we need to maintain connections
|
||||
#
|
||||
@@ -550,40 +638,46 @@ class IPSC(DatagramProtocol):
|
||||
#
|
||||
args = ()
|
||||
|
||||
|
||||
# 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)
|
||||
#
|
||||
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)
|
||||
# General Items
|
||||
self.TS_FLAGS = (self._local['MODE'] + self._local['FLAGS'])
|
||||
#
|
||||
# Peer Link Maintenance Packets
|
||||
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)
|
||||
#
|
||||
# Master Link Maintenance Packets
|
||||
self.MASTER_REG_REPLY_PKT = (MASTER_REG_REPLY + self._local_id + self.TS_FLAGS + str(self._local['NUM_PEERS']) + IPSC_VER)
|
||||
self.MASTER_ALIVE_REPLY_PKT = (MASTER_ALIVE_REPLY + self._local_id + self.TS_FLAGS + IPSC_VER)
|
||||
self.PEER_LIST_REPLY_PKT = (PEER_LIST_REPLY + self._local_id)
|
||||
#
|
||||
# General Link Maintenance Packets
|
||||
self.DE_REG_REQ_PKT = (DE_REG_REQ + self._local_id)
|
||||
self.DE_REG_REPLY_PKT = (DE_REG_REPLY + self._local_id)
|
||||
#
|
||||
logger.info('(%s) IPSC Instance Created', self._network)
|
||||
else:
|
||||
# If we didn't get called correctly, log it!
|
||||
#
|
||||
logger.error('(%s) IPSC Instance Could Not be Created... Exiting', self._network)
|
||||
sys.exit()
|
||||
|
||||
|
||||
# This is called by REACTOR when it starts, We use it to set up the timed
|
||||
# loop for each instance of the IPSC engine
|
||||
#
|
||||
def startProtocol(self):
|
||||
# Timed loops for:
|
||||
# IPSC connection establishment and maintenance
|
||||
# Reporting/Housekeeping
|
||||
#
|
||||
#
|
||||
self._maintenance = task.LoopingCall(self.maintenance_loop)
|
||||
self._maintenance_loop = self._maintenance.start(self._local['ALIVE_TIMER'])
|
||||
#
|
||||
self._reporting = task.LoopingCall(self.reporting_loop)
|
||||
self._reporting_loop = self._reporting.start(10)
|
||||
|
||||
# Choose which set of fucntions to use - authenticated or not
|
||||
if self._local['AUTH_ENABLED']:
|
||||
self.hashed_packet = self.auth_hashed_packet
|
||||
self.strip_hash = self.auth_strip_hash
|
||||
self.validate_auth = self.auth_validate_auth
|
||||
else:
|
||||
self.hashed_packet = self.unauth_hashed_packet
|
||||
self.strip_hash = self.unauth_strip_hash
|
||||
self.validate_auth = self.unauth_validate_auth
|
||||
|
||||
|
||||
#************************************************
|
||||
@@ -606,33 +700,25 @@ class IPSC(DatagramProtocol):
|
||||
logger.debug('(%s) Repeater Wake-Up Packet Received: %s', _network, h(_data))
|
||||
|
||||
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids)
|
||||
_peerid = get_info(int_id(_peerid), peer_ids)
|
||||
_src_sub = get_info(int_id(_src_sub), subscriber_ids)
|
||||
logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', _network, _src_sub, _peerid, _dst_sub)
|
||||
logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', _network, h(_src_sub), h(_peerid), h(_dst_sub))
|
||||
|
||||
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
_dst_sub = get_info(int_id(_dst_sub), subscriber_ids)
|
||||
_peerid = get_info(int_id(_peerid), peer_ids)
|
||||
_src_sub = get_info(int_id(_src_sub), subscriber_ids)
|
||||
logger.debug('(%s) Private Voice Packet Received From: %s, IPSC Peer %s, Destination %s', _network, _src_sub, _peerid, _dst_sub)
|
||||
logger.debug('(%s) Private Voice Packet Received From: %s, IPSC Peer %s, Destination %s', _network, h(_src_sub), h(_peerid), h(_dst_sub))
|
||||
|
||||
def group_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids)
|
||||
_peerid = get_info(int_id(_peerid), peer_ids)
|
||||
_src_sub = get_info(int_id(_src_sub), subscriber_ids)
|
||||
logger.debug('(%s) Group Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, _src_sub, _peerid, _dst_sub)
|
||||
logger.debug('(%s) Group Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, h(_src_sub), h(_peerid), h(_dst_sub))
|
||||
|
||||
def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
_dst_sub = get_info(int_id(_dst_sub), subscriber_ids)
|
||||
_peerid = get_info(int_id(_peerid), peer_ids)
|
||||
_src_sub = get_info(int_id(_src_sub), subscriber_ids)
|
||||
logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, _src_sub, _peerid, _dst_sub)
|
||||
logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, h(_src_sub), h(_peerid), h(_dst_sub))
|
||||
|
||||
def unknown_message(self, _network, _packettype, _peerid, _data):
|
||||
_packettype = h(_packettype)
|
||||
_peerid = get_info(int_id(_peerid), peer_ids)
|
||||
logger.error('(%s) Unknown message type encountered\n\tPacket Type: %s\n\tFrom: %s\n\tPacket: %s', _network, _packettype, _peerid, h(_data))
|
||||
logger.error('(%s) Unknown message type encountered\n\tPacket Type: %s\n\tFrom: %s\n\tPacket: %s', _network, h(_packettype), h(_peerid), h(_data))
|
||||
|
||||
|
||||
#************************************************
|
||||
# IPSC SPECIFIC MAINTENANCE FUNCTIONS
|
||||
#************************************************
|
||||
|
||||
# Reset the outstanding keep-alive counter for _peerid...
|
||||
# Used when receiving acks OR when we see traffic from a repeater, since they ignore keep-alives when transmitting
|
||||
@@ -640,23 +726,28 @@ class IPSC(DatagramProtocol):
|
||||
def reset_keep_alive(self, _peerid):
|
||||
if _peerid in self._peers.keys():
|
||||
self._peers[_peerid]['STATUS']['KEEP_ALIVES_OUTSTANDING'] = 0
|
||||
self._peers[_peerid]['STATUS']['KEEP_ALIVE_RX_TIME'] = int(time.time())
|
||||
if _peerid == self._master['RADIO_ID']:
|
||||
self._master_stat['KEEP_ALIVES_OUTSTANDING'] = 0
|
||||
|
||||
#
|
||||
# NEXT THREE FUNCITONS ARE FOR AUTHENTICATED PACKETS
|
||||
#
|
||||
|
||||
# Take a packet to be SENT, calculate auth hash and return the whole thing
|
||||
#
|
||||
def hashed_packet(self, _key, _data):
|
||||
def auth_hashed_packet(self, _key, _data):
|
||||
_hash = binascii.a2b_hex((hmac_new(_key,_data,sha1)).hexdigest()[:20])
|
||||
return _data + _hash
|
||||
|
||||
# Remove the hash from a packet and return the payload
|
||||
#
|
||||
def strip_hash(self, _data):
|
||||
def auth_strip_hash(self, _data):
|
||||
return _data[:-10]
|
||||
|
||||
# Take a RECEIVED packet, calculate the auth hash and verify authenticity
|
||||
#
|
||||
def validate_auth(self, _key, _data):
|
||||
def auth_validate_auth(self, _key, _data):
|
||||
_payload = self.strip_hash(_data)
|
||||
_hash = _data[-10:]
|
||||
_chk_hash = binascii.a2b_hex((hmac_new(_key,_payload,sha1)).hexdigest()[:20])
|
||||
@@ -665,12 +756,52 @@ class IPSC(DatagramProtocol):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
#************************************************
|
||||
# TIMED LOOP - MY CONNECTION MAINTENANCE
|
||||
#************************************************
|
||||
|
||||
#
|
||||
# NEXT THREE FUNCITONS ARE FOR UN-AUTHENTICATED PACKETS
|
||||
#
|
||||
|
||||
# There isn't a hash to build, so just return the data
|
||||
#
|
||||
def unauth_hashed_packet(self, _key, _data):
|
||||
return _data
|
||||
|
||||
# Remove the hash from a packet and return the payload... except don't
|
||||
#
|
||||
def unauth_strip_hash(self, _data):
|
||||
return _data
|
||||
|
||||
# Everything is validated, so just return True
|
||||
#
|
||||
def unauth_validate_auth(self, _key, _data):
|
||||
return True
|
||||
|
||||
|
||||
#************************************************
|
||||
# TIMED LOOP - CONNECTION MAINTENANCE
|
||||
#************************************************
|
||||
|
||||
# Timed loop initialization (called by the twisted reactor)
|
||||
#
|
||||
def startProtocol(self):
|
||||
# Timed loops for:
|
||||
# IPSC connection establishment and maintenance
|
||||
# Reporting/Housekeeping
|
||||
#
|
||||
if not self._local['MASTER_PEER']:
|
||||
self._peer_maintenance = task.LoopingCall(self.peer_maintenance_loop)
|
||||
self._peer_maintenance_loop = self._peer_maintenance.start(self._local['ALIVE_TIMER'])
|
||||
#
|
||||
if self._local['MASTER_PEER']:
|
||||
self._master_maintenance = task.LoopingCall(self.master_maintenance_loop)
|
||||
self._master_maintenance_loop = self._master_maintenance.start(self._local['ALIVE_TIMER'])
|
||||
#
|
||||
self._reporting = task.LoopingCall(self.reporting_loop)
|
||||
self._reporting_loop = self._reporting.start(10)
|
||||
|
||||
|
||||
# Timed loop used for reporting IPSC status
|
||||
#
|
||||
def reporting_loop(self):
|
||||
# Right now, without this, we really don't know anything is happening.
|
||||
logger.debug('(%s) Periodic Reporting Loop Started', self._network)
|
||||
@@ -678,8 +809,28 @@ class IPSC(DatagramProtocol):
|
||||
print_master(self._network)
|
||||
print_peer_list(self._network)
|
||||
|
||||
def maintenance_loop(self):
|
||||
logger.debug('(%s) Periodic Connection Maintenance Loop Started', self._network)
|
||||
# Timed loop used for IPSC connection Maintenance when we are the MASTER
|
||||
#
|
||||
def master_maintenance_loop(self):
|
||||
logger.debug('(%s) MASTER Connection Maintenance Loop Started', self._network)
|
||||
update_time = int(time.time())
|
||||
|
||||
for peer in self._peers.keys():
|
||||
keep_alive_delta = update_time - self._peers[peer]['STATUS']['KEEP_ALIVE_RX_TIME']
|
||||
logger.debug('(%s) Time Since Last KeepAlive Request from Peer %s: %s seconds', self._network, h(peer), keep_alive_delta)
|
||||
|
||||
if keep_alive_delta > 120:
|
||||
de_register_peer(self._network, peer)
|
||||
peer_list_packet = self.PEER_LIST_REPLY_PKT + build_peer_list(self._peers)
|
||||
peer_list_packet = self.hashed_packet(self._local['AUTH_KEY'], peer_list_packet)
|
||||
send_to_ipsc(self._network, peer_list_packet)
|
||||
logger.warning('(%s) Timeout Exceeded for Peer %s, De-registering', self._network, h(peer))
|
||||
|
||||
|
||||
# Timed loop used for IPSC connection Maintenance when we are a PEER
|
||||
#
|
||||
def peer_maintenance_loop(self):
|
||||
logger.debug('(%s) PEER Connection Maintenance Loop Started', self._network)
|
||||
|
||||
# If the master isn't connected, we have to do that before we can do anything else!
|
||||
#
|
||||
@@ -693,6 +844,7 @@ class IPSC(DatagramProtocol):
|
||||
# Send keep-alive to the master
|
||||
master_alive_packet = self.hashed_packet(self._local['AUTH_KEY'], self.MASTER_ALIVE_PKT)
|
||||
self.transport.write(master_alive_packet, self._master_sock)
|
||||
logger.debug('(%s) Keep Alive Sent to the Master', self._network)
|
||||
|
||||
# If we had a keep-alive outstanding by the time we send another, mark it missed.
|
||||
if (self._master_stat['KEEP_ALIVES_OUTSTANDING']) > 0:
|
||||
@@ -719,47 +871,51 @@ class IPSC(DatagramProtocol):
|
||||
#
|
||||
if (self._master_stat['CONNECTED'] == True) and (self._master_stat['PEER_LIST'] == False):
|
||||
# Ask the master for a peer-list
|
||||
peer_list_req_packet = self.hashed_packet(self._local['AUTH_KEY'], self.PEER_LIST_REQ_PKT)
|
||||
self.transport.write(peer_list_req_packet, self._master_sock)
|
||||
logger.info('(%s), No Peer List - Requesting One From the Master', self._network)
|
||||
if self._local['NUM_PEERS']:
|
||||
peer_list_req_packet = self.hashed_packet(self._local['AUTH_KEY'], self.PEER_LIST_REQ_PKT)
|
||||
self.transport.write(peer_list_req_packet, self._master_sock)
|
||||
logger.info('(%s), No Peer List - Requesting One From the Master', self._network)
|
||||
else:
|
||||
self._master_stat['PEER_LIST'] = True
|
||||
logger.debug('(%s), Skip asking for a Peer List, we are the only Peer', self._network)
|
||||
|
||||
|
||||
# If we do have a peer-list, we need to register with the peers and send keep-alives...
|
||||
#
|
||||
if self._master_stat['PEER_LIST']:
|
||||
# Iterate the list of peers... so we do this for each one.
|
||||
for peer_id in self._peers.keys():
|
||||
peer = self._peers[peer_id]
|
||||
for peer in self._peers.keys():
|
||||
|
||||
# We will show up in the peer list, but shouldn't try to talk to ourselves.
|
||||
if peer_id == self._local_id:
|
||||
if peer == self._local_id:
|
||||
continue
|
||||
|
||||
# If we haven't registered to a peer, send a registration
|
||||
if not peer['STATUS']['CONNECTED']:
|
||||
if not self._peers[peer]['STATUS']['CONNECTED']:
|
||||
peer_reg_packet = self.hashed_packet(self._local['AUTH_KEY'], self.PEER_REG_REQ_PKT)
|
||||
self.transport.write(peer_reg_packet, (peer['IP'], peer['PORT']))
|
||||
logger.info('(%s) Registering with Peer %s', self._network, int_id(peer_id))
|
||||
self.transport.write(peer_reg_packet, (self._peers[peer]['IP'], self._peers[peer]['PORT']))
|
||||
logger.info('(%s) Registering with Peer %s', self._network, int_id(peer))
|
||||
|
||||
# If we have registered with the peer, then send a keep-alive
|
||||
elif peer['STATUS']['CONNECTED']:
|
||||
elif self._peers[peer]['STATUS']['CONNECTED']:
|
||||
peer_alive_req_packet = self.hashed_packet(self._local['AUTH_KEY'], self.PEER_ALIVE_REQ_PKT)
|
||||
self.transport.write(peer_alive_req_packet, (peer['IP'], peer['PORT']))
|
||||
self.transport.write(peer_alive_req_packet, (self._peers[peer]['IP'], self._peers[peer]['PORT']))
|
||||
logger.debug('(%s) Keep-Alive Sent to the Peer %s', self._network, int_id(peer))
|
||||
|
||||
# If we have a keep-alive outstanding by the time we send another, mark it missed.
|
||||
if peer['STATUS']['KEEP_ALIVES_OUTSTANDING'] > 0:
|
||||
peer['STATUS']['KEEP_ALIVES_MISSED'] += 1
|
||||
logger.info('(%s) Peer Keep-Alive Missed for %s', self._network, int_id(peer_id))
|
||||
if self._peers[peer]['STATUS']['KEEP_ALIVES_OUTSTANDING'] > 0:
|
||||
self._peers[peer]['STATUS']['KEEP_ALIVES_MISSED'] += 1
|
||||
logger.info('(%s) Peer Keep-Alive Missed for %s', self._network, int_id(peer))
|
||||
|
||||
# If we have missed too many keep-alives, de-register the peer and start over.
|
||||
if peer['STATUS']['KEEP_ALIVES_OUTSTANDING'] >= self._local['MAX_MISSED']:
|
||||
peer['STATUS']['CONNECTED'] = False
|
||||
if self._peers[peer]['STATUS']['KEEP_ALIVES_OUTSTANDING'] >= self._local['MAX_MISSED']:
|
||||
self._peers[peer]['STATUS']['CONNECTED'] = False
|
||||
#del peer # Becuase once it's out of the dictionary, you can't use it for anything else.
|
||||
logger.warning('(%s) Maximum Peer Keep-Alives Missed -- De-registering the Peer: %s', self._network, int_id(peer_id))
|
||||
logger.warning('(%s) Maximum Peer Keep-Alives Missed -- De-registering the Peer: %s', self._network, int_id(peer))
|
||||
|
||||
# Update our stats before moving on...
|
||||
peer['STATUS']['KEEP_ALIVES_SENT'] += 1
|
||||
peer['STATUS']['KEEP_ALIVES_OUTSTANDING'] += 1
|
||||
self._peers[peer]['STATUS']['KEEP_ALIVES_SENT'] += 1
|
||||
self._peers[peer]['STATUS']['KEEP_ALIVES_OUTSTANDING'] += 1
|
||||
|
||||
|
||||
# For public display of information, etc. - anything not part of internal logging/diagnostics
|
||||
@@ -771,14 +927,15 @@ class IPSC(DatagramProtocol):
|
||||
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.
|
||||
The dict will typically contain the ID of a peer so the origin of the event is known.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
#************************************************
|
||||
# RECEIVED DATAGRAM - ACT IMMEDIATELY!!!
|
||||
#************************************************
|
||||
|
||||
|
||||
#************************************************
|
||||
# MESSAGE RECEIVED - TAKE ACTION
|
||||
#************************************************
|
||||
|
||||
# Actions for received packets by type: For every packet received, there are some things that we need to do:
|
||||
# Decode some of the info
|
||||
@@ -787,25 +944,27 @@ class IPSC(DatagramProtocol):
|
||||
#
|
||||
# Once they're done, we move on to the processing or callbacks for each packet type.
|
||||
#
|
||||
# Callbacks are iterated in the order of "more likely" to "less likely" to reduce processing time
|
||||
#
|
||||
def datagramReceived(self, data, (host, port)):
|
||||
_packettype = data[0:1]
|
||||
_peerid = data[1:5]
|
||||
|
||||
# Authenticate the packet
|
||||
# AUTHENTICATE THE PACKET
|
||||
if not self.validate_auth(self._local['AUTH_KEY'], data):
|
||||
logger.warning('(%s) AuthError: IPSC packet failed authentication. Type %s: Peer ID: %s', self._network, h(_packettype), int(h(_peerid), 16))
|
||||
logger.warning('(%s) AuthError: IPSC packet failed authentication. Type %s: Peer ID: %s', self._network, h(_packettype), int_id(_peerid))
|
||||
return
|
||||
|
||||
# Strip the hash, we won't need it anymore
|
||||
# REMOVE SHA-1 AUTHENTICATION HASH: WE NO LONGER NEED IT
|
||||
data = self.strip_hash(data)
|
||||
|
||||
# Packets types that must be originated from a peer (including master peer)
|
||||
# PACKETS THAT WE RECEIVE FROM ANY VALID PEER OR VALID MASTER
|
||||
if _packettype in ANY_PEER_REQUIRED:
|
||||
if not(valid_master(self._network, _peerid) == False or valid_peer(self._peers.keys(), _peerid) == False):
|
||||
logger.warning('(%s) PeerError: Peer not in peer-list: %s', self._network, int(h(_peerid), 16))
|
||||
logger.warning('(%s) PeerError: Peer not in peer-list: %s', self._network, int_id(_peerid))
|
||||
return
|
||||
|
||||
# User, as in "subscriber" generated packets - a.k.a someone transmitted
|
||||
# ORIGINATED BY SUBSCRIBER UNITS - a.k.a someone transmitted
|
||||
if _packettype in USER_PACKETS:
|
||||
# Extract commonly used items from the packet header
|
||||
_src_sub = data[6:9]
|
||||
@@ -818,33 +977,34 @@ class IPSC(DatagramProtocol):
|
||||
if _packettype == GROUP_VOICE:
|
||||
self.reset_keep_alive(_peerid)
|
||||
self.group_voice(self._network, _src_sub, _dst_sub, _ts, _end, _peerid, data)
|
||||
self._notify_event(self._network, 'group_voice', {'peer_id': int(h(_peerid), 16)})
|
||||
self._notify_event(self._network, 'group_voice', {'peer': int_id(_peerid)})
|
||||
return
|
||||
|
||||
elif _packettype == PVT_VOICE:
|
||||
self.reset_keep_alive(_peerid)
|
||||
self.private_voice(self._network, _src_sub, _dst_sub, _ts, _end, _peerid, data)
|
||||
self._notify_event(self._network, 'private_voice', {'peer_id': int(h(_peerid), 16)})
|
||||
self._notify_event(self._network, 'private_voice', {'peer': int_id(_peerid)})
|
||||
return
|
||||
|
||||
elif _packettype == GROUP_DATA:
|
||||
self.reset_keep_alive(_peerid)
|
||||
self.group_data(self._network, _src_sub, _dst_sub, _ts, _end, _peerid, data)
|
||||
self._notify_event(self._network, 'group_data', {'peer_id': int(h(_peerid), 16)})
|
||||
self._notify_event(self._network, 'group_data', {'peer': int_id(_peerid)})
|
||||
return
|
||||
|
||||
elif _packettype == PVT_DATA:
|
||||
self.reset_keep_alive(_peerid)
|
||||
self.private_data(self._network, _src_sub, _dst_sub, _ts, _end, _peerid, data)
|
||||
self._notify_event(self._network, 'private_voice', {'peer_id': int(h(_peerid), 16)})
|
||||
self._notify_event(self._network, 'private_voice', {'peer': int_id(_peerid)})
|
||||
return
|
||||
return
|
||||
|
||||
# Other peer-required types that we don't do much or anything with yet
|
||||
# MOTOROLA XCMP/XNL CONTROL PROTOCOL: We don't process these (yet)
|
||||
elif _packettype == XCMP_XNL:
|
||||
self.xcmp_xnl(self._network, data)
|
||||
return
|
||||
|
||||
# ORIGINATED BY PEERS, NOT IPSC MAINTENANCE: Call monitoring is all we've found here so far
|
||||
elif _packettype == CALL_MON_ORIGIN:
|
||||
self.call_mon_origin(self._network, data)
|
||||
return
|
||||
@@ -857,30 +1017,33 @@ class IPSC(DatagramProtocol):
|
||||
self.call_mon_nack(self._network, data)
|
||||
return
|
||||
|
||||
# Connection maintenance packets that fall into this category
|
||||
# IPSC CONNECTION MAINTENANCE MESSAGES
|
||||
elif _packettype == DE_REG_REQ:
|
||||
de_register_peer(self._network, _peerid)
|
||||
logger.warning('(%s) Peer De-Registration Request From: %s', self._network, int(h(_peerid), 16))
|
||||
logger.warning('(%s) Peer De-Registration Request From: %s', self._network, int_id(_peerid))
|
||||
return
|
||||
|
||||
elif _packettype == DE_REG_REPLY:
|
||||
logger.warning('(%s) Peer De-Registration Reply From: %s', self._network, int(h(_peerid), 16))
|
||||
logger.warning('(%s) Peer De-Registration Reply From: %s', self._network, int_id(_peerid))
|
||||
return
|
||||
|
||||
elif _packettype == RPT_WAKE_UP:
|
||||
self.repeater_wake_up(self._network, data)
|
||||
logger.debug('(%s) Repeater Wake-Up Packet From: %s', self._network, int(h(_peerid), 16))
|
||||
logger.debug('(%s) Repeater Wake-Up Packet From: %s', self._network, int_id(_peerid))
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
# Packets types that must be originated from a peer
|
||||
#
|
||||
# THE FOLLOWING PACKETS ARE RECEIVED ONLY IF WE ARE OPERATING AS A PEER
|
||||
#
|
||||
|
||||
# ONLY ACCEPT FROM A PREVIOUSLY VALIDATED PEER
|
||||
if _packettype in PEER_REQUIRED:
|
||||
if not valid_peer(self._peers.keys(), _peerid):
|
||||
logger.warning('(%s) PeerError: Peer %s not in peer-list', self._network, int(h(_peerid), 16))
|
||||
logger.warning('(%s) PeerError: Peer %s not in peer-list', self._network, int_id(_peerid))
|
||||
return
|
||||
|
||||
# Packets we send...
|
||||
# REQUESTS FROM PEERS: WE MUST REPLY IMMEDIATELY FOR IPSC MAINTENANCE
|
||||
if _packettype == PEER_ALIVE_REQ:
|
||||
_hex_mode = (data[5])
|
||||
_hex_flags = (data[6:10])
|
||||
@@ -895,54 +1058,66 @@ class IPSC(DatagramProtocol):
|
||||
peer_alive_reply_packet = self.hashed_packet(self._local['AUTH_KEY'], self.PEER_ALIVE_REPLY_PKT)
|
||||
self.transport.write(peer_alive_reply_packet, (host, port))
|
||||
self.reset_keep_alive(_peerid) # Might as well reset our own counter, we know it's out there...
|
||||
logger.debug('(%s) Keep-Alive reply sent to Peer %s', self._network, int_id(_peerid))
|
||||
return
|
||||
|
||||
elif _packettype == PEER_REG_REQ:
|
||||
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))
|
||||
logger.info('(%s) Peer Registration Request From: %s', self._network, int(h(_peerid), 16))
|
||||
logger.info('(%s) Peer Registration Request From: %s', self._network, int_id(_peerid))
|
||||
return
|
||||
|
||||
# Packets we receive...
|
||||
# ANSWERS FROM REQUESTS WE SENT TO PEERS: WE DO NOT REPLY
|
||||
elif _packettype == PEER_ALIVE_REPLY:
|
||||
self.reset_keep_alive(_peerid)
|
||||
self._peers[_peerid]['STATUS']['KEEP_ALIVES_RECEIVED'] += 1
|
||||
self._peers[_peerid]['STATUS']['KEEP_ALIVE_RX_TIME'] = int(time.time())
|
||||
logger.debug('(%s) Keep-Alive Reply (we sent the request) Received from Peer %s', self._network, int_id(_peerid))
|
||||
return
|
||||
|
||||
elif _packettype == PEER_REG_REPLY:
|
||||
if _peerid in self._peers.keys():
|
||||
self._peers[_peerid]['STATUS']['CONNECTED'] = True
|
||||
logger.info('(%s) Registration Reply From: %s', self._network, int(h(_peerid), 16))
|
||||
logger.info('(%s) Registration Reply From: %s', self._network, int_id(_peerid))
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
# PACKETS ONLY ACCEPTED FROM OUR MASTER
|
||||
|
||||
# Packets types that must be originated from a Master
|
||||
# Packets we receive...
|
||||
# PACKETS WE ONLY ACCEPT IF WE HAVE FINISHED REGISTERING WITH OUR MASTER
|
||||
if _packettype in MASTER_REQUIRED:
|
||||
if not valid_master(self._network, _peerid):
|
||||
logger.warning('(%s) MasterError: %s is not the master peer', self._network, int(h(_peerid), 16))
|
||||
logger.warning('(%s) MasterError: %s is not the master peer', self._network, int_id(_peerid))
|
||||
return
|
||||
|
||||
|
||||
# ANSWERS FROM REQUESTS WE SENT TO THE MASTER: WE DO NOT REPLY
|
||||
if _packettype == MASTER_ALIVE_REPLY:
|
||||
self.reset_keep_alive(_peerid)
|
||||
self._master['STATUS']['KEEP_ALIVES_RECEIVED'] += 1
|
||||
self._master['STATUS']['KEEP_ALIVE_RX_TIME'] = int(time.time())
|
||||
logger.debug('(%s) Keep-Alive Reply (we sent the request) Received from the Master %s', self._network, int_id(_peerid))
|
||||
return
|
||||
|
||||
elif _packettype == PEER_LIST_REPLY:
|
||||
NETWORK[self._network]['MASTER']['STATUS']['PEER_LIST'] = True
|
||||
if len(data) > 18:
|
||||
process_peer_list(data, self._network)
|
||||
logger.debug('(%s) Peer List Reply Recieved From Master %s', self._network, int_id(_peerid))
|
||||
return
|
||||
return
|
||||
|
||||
|
||||
# When we hear from the master, record it's ID, flag that we're connected, and reset the dead counter.
|
||||
# THIS MEANS WE HAVE SUCCESSFULLY REGISTERED TO OUR MASTER - RECORD MASTER INFORMATION
|
||||
elif _packettype == MASTER_REG_REPLY:
|
||||
|
||||
_hex_mode = (data[5])
|
||||
_hex_flags = (data[6:10])
|
||||
_hex_mode = data[5]
|
||||
_hex_flags = data[6:10]
|
||||
_num_peers = data[10:12]
|
||||
_decoded_mode = process_mode_byte(_hex_mode)
|
||||
_decoded_flags = process_flags_bytes(_hex_flags)
|
||||
|
||||
|
||||
self._local['NUM_PEERS'] = int(h(_num_peers), 16)
|
||||
self._master['RADIO_ID'] = _peerid
|
||||
self._master['MODE'] = _hex_mode
|
||||
self._master['MODE_DECODE'] = _decoded_mode
|
||||
@@ -950,42 +1125,84 @@ class IPSC(DatagramProtocol):
|
||||
self._master['FLAGS_DECODE'] = _decoded_flags
|
||||
self._master_stat['CONNECTED'] = True
|
||||
self._master_stat['KEEP_ALIVES_OUTSTANDING'] = 0
|
||||
logger.warning('(%s) Registration response (we requested reg) from the Master %s (%s peers)', self._network, int_id(_peerid), self._local['NUM_PEERS'])
|
||||
return
|
||||
|
||||
# We know about these types, but absolutely don't take an action
|
||||
|
||||
# THE FOLLOWING PACKETS ARE RECEIVED ONLLY IF WE ARE OPERATING AS A MASTER
|
||||
|
||||
# REQUESTS FROM PEERS: WE MUST REPLY IMMEDIATELY FOR IPSC MAINTENANCE
|
||||
|
||||
# REQUEST TO REGISTER TO THE IPSC
|
||||
elif _packettype == MASTER_REG_REQ:
|
||||
# We can't operate as a master as of now, so we should never receive one of these.
|
||||
logger.debug('(%s) Master Registration Packet Received - WE ARE NOT A MASTER!', self._network)
|
||||
return
|
||||
|
||||
# If there's a packet type we don't know about, it should be logged so we can figure it out and take an appropriate action!
|
||||
_ip_address = host
|
||||
_port = port
|
||||
_hex_mode = data[5]
|
||||
_hex_flags = data[6:10]
|
||||
_decoded_mode = process_mode_byte(_hex_mode)
|
||||
_decoded_flags = process_flags_bytes(_hex_flags)
|
||||
|
||||
master_reg_reply_packet = self.hashed_packet(self._local['AUTH_KEY'], self.MASTER_REG_REPLY_PKT)
|
||||
self.transport.write(master_reg_reply_packet, (host, port))
|
||||
logger.debug('(%s) Master Registration Packet Received from peer %s', self._network, int_id(_peerid))
|
||||
|
||||
# If this entry was NOT already in our list, add it.
|
||||
if _peerid not in self._peers.keys():
|
||||
self._peers[_peerid] = {
|
||||
'IP': _ip_address,
|
||||
'PORT': _port,
|
||||
'MODE': _hex_mode,
|
||||
'MODE_DECODE': _decoded_mode,
|
||||
'FLAGS': _hex_flags,
|
||||
'FLAGS_DECODE': _decoded_flags,
|
||||
'STATUS': {
|
||||
'CONNECTED': True,
|
||||
'KEEP_ALIVES_SENT': 0,
|
||||
'KEEP_ALIVES_MISSED': 0,
|
||||
'KEEP_ALIVES_OUTSTANDING': 0,
|
||||
'KEEP_ALIVES_RECEIVED': 0,
|
||||
'KEEP_ALIVE_RX_TIME': int(time.time())
|
||||
}
|
||||
}
|
||||
self._local['NUM_PEERS'] = len(self._peers)
|
||||
logger.debug('(%s) Peer Added To Peer List: %s (IPSC now has %s Peers)', self._network, self._peers[_peerid], self._local['NUM_PEERS'])
|
||||
return
|
||||
|
||||
# REQUEST FOR A KEEP-ALIVE REPLY (WE KNOW THE PEER IS STILL ALIVE TOO)
|
||||
elif _packettype == MASTER_ALIVE_REQ:
|
||||
if _peerid in self._peers.keys():
|
||||
self._peers[_peerid]['STATUS']['KEEP_ALIVES_RECEIVED'] += 1
|
||||
self._peers[_peerid]['STATUS']['KEEP_ALIVE_RX_TIME'] = int(time.time())
|
||||
|
||||
master_alive_reply_packet = self.hashed_packet(self._local['AUTH_KEY'], self.MASTER_ALIVE_REPLY_PKT)
|
||||
self.transport.write(master_alive_reply_packet, (host, port))
|
||||
|
||||
logger.debug('(%s) Master Keep-Alive Request Received from peer %s', self._network, int_id(_peerid))
|
||||
else:
|
||||
logger.warning('(%s) Master Keep-Alive Request Received from *UNREGISTERED* peer %s', self._network, int_id(_peerid))
|
||||
return
|
||||
|
||||
# REQUEST FOR A PEER LIST
|
||||
elif _packettype == PEER_LIST_REQ:
|
||||
|
||||
if _peerid in self._peers.keys():
|
||||
logger.debug('(%s) Peer List Request from peer %s', self._network, int_id(_peerid))
|
||||
peer_list_packet = self.PEER_LIST_REPLY_PKT + build_peer_list(self._peers)
|
||||
peer_list_packet = self.hashed_packet(self._local['AUTH_KEY'], peer_list_packet)
|
||||
send_to_ipsc(self._network, peer_list_packet)
|
||||
else:
|
||||
logger.warning('(%s) Peer List Request Received from *UNREGISTERED* peer %s', self._network, int_id(_peerid))
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
# PACKET IS OF AN UNKNOWN TYPE. LOG IT AND IDENTTIFY IT!
|
||||
else:
|
||||
self.unknown_message(self._network, _packettype, _peerid, data)
|
||||
return
|
||||
|
||||
|
||||
#************************************************
|
||||
# Derived Class
|
||||
# used in the rare event of an
|
||||
# unauthenticated IPSC network.
|
||||
#************************************************
|
||||
|
||||
class UnauthIPSC(IPSC):
|
||||
|
||||
# There isn't a hash to build, so just return the data
|
||||
#
|
||||
def hashed_packet(self, _key, _data):
|
||||
return _data
|
||||
|
||||
# Remove the hash from a packet and return the payload... except don't
|
||||
#
|
||||
def strip_hash(self, _data):
|
||||
return _data
|
||||
|
||||
# Everything is validated, so just return True
|
||||
#
|
||||
def validate_auth(self, _key, _data):
|
||||
return True
|
||||
|
||||
|
||||
#************************************************
|
||||
@@ -993,13 +1210,15 @@ class UnauthIPSC(IPSC):
|
||||
#************************************************
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.info('DMRlink \'dmrlink.py\' (c) 2013 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
logger.info('DMRlink \'dmrlink.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
|
||||
# Set signal handers so that we can gracefully exit if need be
|
||||
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||
signal.signal(sig, handler)
|
||||
|
||||
networks = {}
|
||||
for ipsc_network in NETWORK:
|
||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
||||
if NETWORK[ipsc_network]['LOCAL']['AUTH_ENABLED']:
|
||||
networks[ipsc_network] = IPSC(ipsc_network)
|
||||
else:
|
||||
networks[ipsc_network] = UnauthIPSC(ipsc_network)
|
||||
networks[ipsc_network] = IPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network])
|
||||
reactor.run()
|
||||
+2
-13
@@ -72,22 +72,11 @@ LOG_LEVEL: CRITICAL
|
||||
# MASTER_PEER: Must be False, we cannot yet act as a master peer.
|
||||
# AUTH_ENABLED: Do we use authenticated IPSC?
|
||||
# AUTH_KEY: The Authentication key (up to 40 hex characters)
|
||||
# MASTER_IP: IP address of the IPSC master
|
||||
# MASTER_PORT: UDP port of the IPSC master
|
||||
# MASTER_IP: IP address of the IPSC master (ignored if DMRlink is the master)
|
||||
# MASTER_PORT: UDP port of the IPSC master (ignored if DMRlinkn is the master)
|
||||
#
|
||||
# ...Repeat the block for each IPSC network to join.
|
||||
#
|
||||
[IPSC1]
|
||||
ENABLED: True
|
||||
RADIO_ID: 1
|
||||
PORT: 50000
|
||||
ALIVE_TIMER: 5
|
||||
TS1_LINK: True
|
||||
TS2_LINK: True
|
||||
AUTH_ENABLED: True
|
||||
AUTH_KEY: 1
|
||||
MASTER_IP: 1.2.3.4
|
||||
MASTER_PORT: 50000
|
||||
|
||||
[IPSC1]
|
||||
ENABLED: True
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
This is the internal structure dmrlink uses to hold master, peer and local information for each IPSC. the actual numbers are bogus, and the encoded FLAGS and MODE bytes don't match the binary decoding. This example is only to illustrate and document the struture in a "pretty print" type format only. Yeah, you could just pretty print it, but this is a little cleaner and you don't have to go in and take the extra 5 mintues this way.
|
||||
|
||||
{
|
||||
'MASTER': {
|
||||
'STATUS': {
|
||||
'KEEP_ALIVES_OUTSTANDING': 1,
|
||||
'KEEP_ALIVES_MISSED': 0,
|
||||
'CONNECTED': True,
|
||||
'KEEP_ALIVES_SENT': 10,
|
||||
'PEER_LIST': True },
|
||||
'MODE_DECODE': {
|
||||
'TS_1': True,
|
||||
'TS_2': True,
|
||||
'PEER_MODE': 'DIGITAL',
|
||||
'PEER_OP': True },
|
||||
'FLAGS_DECODE': {
|
||||
'VOICE': True,
|
||||
'RCM': True,
|
||||
'XNL_SLAVE': True,
|
||||
'MASTER': True,
|
||||
'CON_APP': True,
|
||||
'XNL_CON': False,
|
||||
'CSBK': True,
|
||||
'DATA': True,
|
||||
'XNL_MASTER': False,
|
||||
'AUTH': True },
|
||||
'IP': '10.10.10.1',
|
||||
'RADIO_ID': '\x00\x00\x00\x01',
|
||||
'FLAGS': '\x00\x00\xe0\x3d',
|
||||
'MODE': '\x6a',
|
||||
'PORT': 50001 },
|
||||
'PEERS': {
|
||||
'\x00\x00\x01\x03': {
|
||||
'STATUS': {
|
||||
'KEEP_ALIVES_OUTSTANDING': 1,
|
||||
'KEEP_ALIVES_MISSED': 0,
|
||||
'CONNECTED': True,
|
||||
'KEEP_ALIVES_SENT': 8 },
|
||||
'MODE_DECODE': {
|
||||
'TS_1': True,
|
||||
'TS_2': True,
|
||||
'PEER_MODE': 'DIGITAL',
|
||||
'PEER_OP': True },
|
||||
'FLAGS_DECODE': {
|
||||
'VOICE': True,
|
||||
'RCM': False,
|
||||
'XNL_SLAVE': False,
|
||||
'MASTER': False,
|
||||
'CON_APP': True,
|
||||
'XNL_CON': False,
|
||||
'CSBK': False,
|
||||
'DATA': True,
|
||||
'XNL_MASTER': False,
|
||||
'AUTH': True },
|
||||
'IP': '10.10.20.1',
|
||||
'FLAGS': '\x00\x00\x00\x1c',
|
||||
'MODE': '\x6a',
|
||||
'PORT': 51990 },
|
||||
'\x00\x00\x05\x80': {
|
||||
'STATUS': {
|
||||
'KEEP_ALIVES_OUTSTANDING': 1,
|
||||
'KEEP_ALIVES_MISSED': 0,
|
||||
'CONNECTED': True,
|
||||
'KEEP_ALIVES_SENT': 8},
|
||||
'MODE_DECODE': {
|
||||
'TS_1': True,
|
||||
'TS_2': True,
|
||||
'PEER_MODE': 'DIGITAL',
|
||||
'PEER_OP': True },
|
||||
'FLAGS_DECODE': {
|
||||
'VOICE': True,
|
||||
'RCM': False,
|
||||
'XNL_SLAVE': False,
|
||||
'MASTER': False,
|
||||
'CON_APP': True,
|
||||
'XNL_CON': False,
|
||||
'CSBK': False,
|
||||
'DATA': True,
|
||||
'XNL_MASTER': False,
|
||||
'AUTH': True },
|
||||
'IP': '10.10.20.2',
|
||||
'FLAGS': '\x00\x00\x00\x01',
|
||||
'MODE': '\x6a',
|
||||
'PORT': 50900 },
|
||||
'\x00\x04\xa2\x37': {
|
||||
'STATUS': {
|
||||
'KEEP_ALIVES_OUTSTANDING': 1,
|
||||
'KEEP_ALIVES_MISSED': 0,
|
||||
'CONNECTED': True,
|
||||
'KEEP_ALIVES_SENT': 8 },
|
||||
'MODE_DECODE': {
|
||||
'TS_1': True,
|
||||
'TS_2': True,
|
||||
'PEER_MODE': 'DIGITAL',
|
||||
'PEER_OP': True },
|
||||
'FLAGS_DECODE': {
|
||||
'VOICE': True,
|
||||
'RCM': False,
|
||||
'XNL_SLAVE': False,
|
||||
'MASTER': False,
|
||||
'CON_APP': False,
|
||||
'XNL_CON': False,
|
||||
'CSBK': True,
|
||||
'DATA': True,
|
||||
'XNL_MASTER': True,
|
||||
'AUTH': True },
|
||||
'IP': '10.10.20.3',
|
||||
'FLAGS': '\x00\x00\x00\x01',
|
||||
'MODE': '\x6a',
|
||||
'PORT': 50000 },
|
||||
'LOCAL': {
|
||||
'TS2_LINK': True,
|
||||
'AUTH_KEY': '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xab\xcd\xef\xf0',
|
||||
'CON_APP': True,
|
||||
'RADIO_ID': '\x00\x67\x03',
|
||||
'ENABLED': True,
|
||||
'ALIVE_TIMER': 5,
|
||||
'TS1_LINK': True,
|
||||
'RCM': True,
|
||||
'AUTH_ENABLED': True,
|
||||
'IPSC_MODE': 'DIGITAL',
|
||||
'DATA_CALL': True,
|
||||
'NUM_PEERS': 6,
|
||||
'PORT': 50001,
|
||||
'VOICE_CALL': True,
|
||||
'MASTER_PEER': False,
|
||||
'CSBK_CALL': True,
|
||||
'XNL_CALL': True,
|
||||
'XNL_MASTER': True,
|
||||
'MODE': '\x6a',
|
||||
'MAX_MISSED': 20,
|
||||
'FLAGS': '\x00\x00\xe0\xdc',
|
||||
'PEER_OPER': True }
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2013 Cortney T. Buffington, N0MJS and the K0USY Group. n0mjs@me.com
|
||||
# Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group. 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
|
||||
@@ -19,8 +19,8 @@ PVT_DATA = '\x84'
|
||||
RPT_WAKE_UP = '\x85' # Similar to OTA DMR "wake up"
|
||||
MASTER_REG_REQ = '\x90' # FROM peer TO master
|
||||
MASTER_REG_REPLY = '\x91' # FROM master TO peer
|
||||
PEER_LIST_REQ = '\x92'
|
||||
PEER_LIST_REPLY = '\x93'
|
||||
PEER_LIST_REQ = '\x92' # From peer TO master
|
||||
PEER_LIST_REPLY = '\x93' # From master TO peer
|
||||
PEER_REG_REQ = '\x94' # Peer registration request
|
||||
PEER_REG_REPLY = '\x95' # Peer registration reply
|
||||
MASTER_ALIVE_REQ = '\x96' # FROM peer TO master
|
||||
@@ -58,6 +58,48 @@ MASTER_REQUIRED = [PEER_LIST_REPLY, MASTER_ALIVE_REPLY]
|
||||
# User-Generated Packet Types
|
||||
USER_PACKETS = [GROUP_VOICE, PVT_VOICE, GROUP_DATA, PVT_DATA]
|
||||
|
||||
# RCM (Repeater Call Monitor) Constants
|
||||
|
||||
TS = {
|
||||
'\x00': '1',
|
||||
'\x01': '2'
|
||||
}
|
||||
|
||||
NACK = {
|
||||
'\x05': 'BSID Start',
|
||||
'\x06': 'BSID End'
|
||||
}
|
||||
|
||||
TYPE = {
|
||||
'\x30': 'Private Data Set-Up',
|
||||
'\x31': 'Group Data Set-Up',
|
||||
'\x32': 'Private CSBK Set-Up',
|
||||
'\x47': 'Radio Check Request',
|
||||
'\x45': 'Call Alert',
|
||||
'\x4D': 'Remote Monitor Request',
|
||||
'\x4F': 'Group Voice',
|
||||
'\x50': 'Private Voice',
|
||||
'\x51': 'Group Data',
|
||||
'\x52': 'Private Data',
|
||||
'\x53': 'All Call'
|
||||
}
|
||||
|
||||
SEC = {
|
||||
'\x00': 'None',
|
||||
'\x01': 'Basic',
|
||||
'\x02': 'Enhanced'
|
||||
}
|
||||
|
||||
STATUS = {
|
||||
'\x01': 'Active',
|
||||
'\x02': 'End',
|
||||
'\x05': 'TS In Use',
|
||||
'\x0A': 'BSID ON',
|
||||
'\x0B': 'Timeout',
|
||||
'\x0C': 'TX Interrupt'
|
||||
}
|
||||
|
||||
|
||||
# Conditions for accepting certain types of messages... the cornerstone of a secure IPSC system :)
|
||||
'''
|
||||
REQ_VALID_PEER = [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2013 Cortney T. Buffington, N0MJS and the K0USY Group. n0mjs@me.com
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This work is licensed under the Creative Commons Attribution-ShareAlike
|
||||
# 3.0 Unported License.To view a copy of this license, visit
|
||||
@@ -13,7 +13,16 @@ from twisted.internet import reactor
|
||||
from binascii import b2a_hex as h
|
||||
|
||||
import time
|
||||
from dmrlink import IPSC, UnauthIPSC, NETWORK, networks, get_info, int_id, subscriber_ids, peer_ids, talkgroup_ids, logger
|
||||
from dmrlink import IPSC, NETWORK, networks, get_info, int_id, subscriber_ids, peer_ids, talkgroup_ids, logger
|
||||
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
__credits__ = 'Adam Fast, KC0YLK, Dave K, and he who wishes not to be named'
|
||||
__license__ = 'Creative Commons Attribution-ShareAlike 3.0 Unported'
|
||||
__version__ = '0.2a'
|
||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||
__email__ = 'n0mjs@me.com'
|
||||
__status__ = 'Production'
|
||||
|
||||
class logIPSC(IPSC):
|
||||
|
||||
@@ -71,29 +80,11 @@ class logIPSC(IPSC):
|
||||
_src_sub = get_info(int_id(_src_sub), subscriber_ids)
|
||||
print('({}) Private Data Packet Received From: {} To: {}' .format(_network, _src_sub, _dst_sub))
|
||||
|
||||
class logUnauthIPSC(logIPSC):
|
||||
|
||||
# There isn't a hash to build, so just return the data
|
||||
#
|
||||
def hashed_packet(self, _key, _data):
|
||||
return _data
|
||||
|
||||
# Remove the hash from a packet and return the payload... except don't
|
||||
#
|
||||
def strip_hash(self, _data):
|
||||
return _data
|
||||
|
||||
# Everything is validated, so just return True
|
||||
#
|
||||
def validate_auth(self, _key, _data):
|
||||
return True
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.info('DMRlink \'log.py\' (c) 2013 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
logger.info('DMRlink \'log.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
for ipsc_network in NETWORK:
|
||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
||||
if NETWORK[ipsc_network]['LOCAL']['AUTH_ENABLED']:
|
||||
networks[ipsc_network] = logIPSC(ipsc_network)
|
||||
else:
|
||||
networks[ipsc_network] = logUnauthIPSC(ipsc_network)
|
||||
networks[ipsc_network] = logIPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network])
|
||||
reactor.run()
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# 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.
|
||||
|
||||
# This is a sample application that "records" and replays transmissions for testing.
|
||||
|
||||
from __future__ import print_function
|
||||
from twisted.internet import reactor
|
||||
from binascii import b2a_hex as h
|
||||
|
||||
import sys, time
|
||||
from dmrlink import IPSC, NETWORK, networks, logger, dmr_nat, int_id, send_to_ipsc, hex_str_3
|
||||
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
__credits__ = 'Adam Fast, KC0YLK; Dave K; and he who wishes not to be named'
|
||||
__license__ = 'Creative Commons Attribution-ShareAlike 3.0 Unported'
|
||||
__version__ = '0.1b'
|
||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||
__email__ = 'n0mjs@me.com'
|
||||
__status__ = 'pre-alpha'
|
||||
|
||||
|
||||
try:
|
||||
from playback_config import *
|
||||
except ImportError:
|
||||
sys.exit('Configuration file not found or invalid')
|
||||
|
||||
HEX_TGID = hex_str_3(TGID)
|
||||
|
||||
class playbackIPSC(IPSC):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
IPSC.__init__(self, *args, **kwargs)
|
||||
self.CALL_DATA = []
|
||||
|
||||
#************************************************
|
||||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||
#************************************************
|
||||
#
|
||||
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
if HEX_TGID == _dst_sub and TS == _ts:
|
||||
if not _end:
|
||||
if not self.CALL_DATA:
|
||||
logger.info('(%s) Receiving transmission to be played back from subscriber: %s', _network, int_id(_src_sub))
|
||||
_tmp_data = _data
|
||||
#_tmp_data = dmr_nat(_data, _src_sub, NETWORK[_network]['LOCAL']['RADIO_ID'])
|
||||
self.CALL_DATA.append(_tmp_data)
|
||||
if _end:
|
||||
self.CALL_DATA.append(_data)
|
||||
time.sleep(2)
|
||||
logger.info('(%s) Playing back transmission from subscriber: %s', _network, int_id(_src_sub))
|
||||
for i in self.CALL_DATA:
|
||||
_tmp_data = i
|
||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_network]['LOCAL']['RADIO_ID'])
|
||||
_tmp_data = self.hashed_packet(NETWORK[_network]['LOCAL']['AUTH_KEY'], _tmp_data)
|
||||
# Send the packet to all peers in the target IPSC
|
||||
send_to_ipsc(_network, _tmp_data)
|
||||
time.sleep(0.06)
|
||||
self.CALL_DATA = []
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.info('DMRlink \'playback.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
for ipsc_network in NETWORK:
|
||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
||||
networks[ipsc_network] = playbackIPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network])
|
||||
reactor.run()
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# THESE ARE THE THINGS THAT YOU NEED TO CONFIGURE TO USE playback.py
|
||||
# TGID TO LISTEN FOR AND REPEAT ON
|
||||
TGID = 10
|
||||
# TIMESLOT TO LISTEN FOR AND REPEAT ON
|
||||
TS = 0
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2013 Cortney T. Buffington, N0MJS and the K0USY Group. n0mjs@me.com
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This work is licensed under the Creative Commons Attribution-ShareAlike
|
||||
# 3.0 Unported License.To view a copy of this license, visit
|
||||
@@ -19,49 +19,21 @@ from binascii import b2a_hex as h
|
||||
import time
|
||||
import binascii
|
||||
import dmrlink
|
||||
from dmrlink import IPSC, UnauthIPSC, NETWORK, networks, get_info, int_id, subscriber_ids, peer_ids, talkgroup_ids, logger
|
||||
from dmrlink import IPSC, NETWORK, networks, get_info, int_id, subscriber_ids, peer_ids, talkgroup_ids, logger
|
||||
|
||||
# Constants
|
||||
|
||||
TS = {
|
||||
'\x00': '1',
|
||||
'\x01': '2'
|
||||
}
|
||||
|
||||
NACK = {
|
||||
'\x05': 'BSID Start',
|
||||
'\x06': 'BSID End'
|
||||
}
|
||||
|
||||
TYPE = {
|
||||
'\x30': 'Private Data Set-Up',
|
||||
'\x31': 'Group Data Set-Up',
|
||||
'\x32': 'Private CSBK Set-Up',
|
||||
'\x47': 'Radio Check Request',
|
||||
'\x45': 'Call Alert',
|
||||
'\x4D': 'Remote Monitor Request',
|
||||
'\x4F': 'Group Voice',
|
||||
'\x50': 'Private Voice',
|
||||
'\x51': 'Group Data',
|
||||
'\x52': 'Private Data',
|
||||
'\x53': 'All Call'
|
||||
}
|
||||
|
||||
SEC = {
|
||||
'\x00': 'None',
|
||||
'\x01': 'Basic',
|
||||
'\x02': 'Enhanced'
|
||||
}
|
||||
|
||||
STATUS = {
|
||||
'\x01': 'Active',
|
||||
'\x02': 'End',
|
||||
'\x05': 'TS In Use',
|
||||
'\x0A': 'BSID ON',
|
||||
'\x0B': 'Timeout',
|
||||
'\x0C': 'TX Interrupt'
|
||||
}
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
__credits__ = 'Adam Fast, KC0YLK, Dave K, and he who wishes not to be named'
|
||||
__license__ = 'Creative Commons Attribution-ShareAlike 3.0 Unported'
|
||||
__version__ = '0.2a'
|
||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||
__email__ = 'n0mjs@me.com'
|
||||
__status__ = 'Production'
|
||||
|
||||
try:
|
||||
from ipsc.ipsc_message_types import *
|
||||
except ImportError:
|
||||
sys.exit('IPSC message types file not found or invalid')
|
||||
|
||||
class rcmIPSC(IPSC):
|
||||
|
||||
@@ -71,7 +43,7 @@ class rcmIPSC(IPSC):
|
||||
#************************************************
|
||||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||
#************************************************
|
||||
|
||||
#
|
||||
def call_mon_origin(self, _network, _data):
|
||||
_source = _data[1:5]
|
||||
_ipsc_src = _data[5:9]
|
||||
@@ -99,30 +71,6 @@ class rcmIPSC(IPSC):
|
||||
print('Source Sub: ', _rf_src)
|
||||
print('Target Sub: ', _rf_tgt)
|
||||
print()
|
||||
|
||||
def call_mon_rpt(self, _network, _data):
|
||||
#print('({}) Repeater Call Monitor Repeating Packet: {}' .format(_network, h(_data)))
|
||||
pass
|
||||
|
||||
def call_mon_nack(self, _network, _data):
|
||||
#print('({}) Repeater Call Monitor NACK Packet: {}' .format(_network, h(_data)))
|
||||
pass
|
||||
|
||||
def xcmp_xnl(self, _network, _data):
|
||||
#print('({}) XCMP/XNL Packet Received From: {}' .format(_network, h(_data)))
|
||||
pass
|
||||
|
||||
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
pass
|
||||
|
||||
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
pass
|
||||
|
||||
def group_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
pass
|
||||
|
||||
def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
pass
|
||||
|
||||
def repeater_wake_up(self, _network, _data):
|
||||
_source = _data[1:5]
|
||||
@@ -130,29 +78,11 @@ class rcmIPSC(IPSC):
|
||||
_source_name = get_info(_source_dec, peer_ids)
|
||||
print('({}) Repeater Wake-Up Packet Received: {} ({})' .format(_network, _source_name, _source_dec))
|
||||
|
||||
class rcmUnauthIPSC(rcmIPSC):
|
||||
|
||||
# There isn't a hash to build, so just return the data
|
||||
#
|
||||
def hashed_packet(self, _key, _data):
|
||||
return _data
|
||||
|
||||
# Remove the hash from a packet and return the payload... except don't
|
||||
#
|
||||
def strip_hash(self, _data):
|
||||
return _data
|
||||
|
||||
# Everything is validated, so just return True
|
||||
#
|
||||
def validate_auth(self, _key, _data):
|
||||
return True
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.info('DMRlink \'rcm.py\' (c) 2013 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
logger.info('DMRlink \'rcm.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
for ipsc_network in NETWORK:
|
||||
if (NETWORK[ipsc_network]['LOCAL']['ENABLED']):
|
||||
if NETWORK[ipsc_network]['LOCAL']['AUTH_ENABLED'] == True:
|
||||
networks[ipsc_network] = rcmIPSC(ipsc_network)
|
||||
else:
|
||||
networks[ipsc_network] = rcmUnauthIPSC(ipsc_network)
|
||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
||||
networks[ipsc_network] = rcmIPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network])
|
||||
reactor.run()
|
||||
+1
-1
@@ -1 +1 @@
|
||||
Worldwide,1
|
||||
Worldwide,1
|
||||
|
Reference in New Issue
Block a user