From 134a34f22e5125e687cf54687a046d275d208d5e Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Wed, 14 May 2014 14:18:33 -0500 Subject: [PATCH] Work on Master Support --- dmrlink.py | 157 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 101 insertions(+), 56 deletions(-) diff --git a/dmrlink.py b/dmrlink.py index e1e20aa..398610c 100755 --- a/dmrlink.py +++ b/dmrlink.py @@ -547,6 +547,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 # @@ -613,22 +617,6 @@ class IPSC(DatagramProtocol): self.validate_auth = self.unauth_validate_auth - # 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 - # - if not self._local['MASTER_PEER']: - 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) - - #************************************************ # CALLBACK FUNCTIONS FOR USER PACKET TYPES #************************************************ @@ -664,6 +652,11 @@ class IPSC(DatagramProtocol): _packettype = h(_packettype) logger.error('(%s) Unknown message type encountered\n\tPacket Type: %s\n\tFrom: %s\n\tPacket: %s', _network, _packettype, _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 # @@ -720,17 +713,36 @@ class IPSC(DatagramProtocol): return True -#************************************************ -# TIMED LOOP - MY CONNECTION MAINTENANCE -#************************************************ + #************************************************ + # 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._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) + + # 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) if REPORTS['REPORT_PEERS']: print_master(self._network) print_peer_list(self._network) - + + # Timed loop used for IPSC connection Maintenance if we are a PEER + # def maintenance_loop(self): logger.debug('(%s) Periodic Connection Maintenance Loop Started', self._network) @@ -773,9 +785,13 @@ 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... @@ -830,10 +846,11 @@ class IPSC(DatagramProtocol): """ 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 @@ -842,25 +859,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_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_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] @@ -895,11 +914,12 @@ class IPSC(DatagramProtocol): 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 @@ -912,7 +932,7 @@ 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_id(_peerid)) @@ -928,14 +948,17 @@ class IPSC(DatagramProtocol): 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_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]) @@ -959,7 +982,7 @@ class IPSC(DatagramProtocol): 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) logger.debug('(%s) Keep-Alive Reply (we sent the request) Received from Peer %s', self._network, int_id(_peerid)) @@ -971,15 +994,17 @@ class IPSC(DatagramProtocol): 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_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) logger.debug('(%s) Keep-Alive Reply (we sent the request) Received from the Master %s', self._network, int_id(_peerid)) @@ -994,7 +1019,7 @@ class IPSC(DatagramProtocol): 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] @@ -1014,42 +1039,62 @@ class IPSC(DatagramProtocol): logger.warning('(%s) Registration response (we requested reg) from the Master %s (%s peers)', self._network, int_id(_peerid), self._local['NUM_PEERS']) return - # This is if we operate as a master... work in progress + + # 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: + _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) - ''' BELOW STOLEN FROM MASTER_REG_REPLY - MUST PARSE TO SEE IF THE PEER EXISTS AND IF NOT, THEN - WE NEED TO ADD IT TO THE PEER LIST WE KEEP IN OUR DATA STRUCTURE... WORK STARTS HERE - - self._master['RADIO_ID'] = _peerid - self._master['MODE'] = _hex_mode - self._master['MODE_DECODE'] = _decoded_mode - self._master['FLAGS'] = _hex_flags - self._master['FLAGS_DECODE'] = _decoded_flags - self._master_stat['CONNECTED'] = True - self._master_stat['KEEP_ALIVES_OUTSTANDING'] = 0 - ''' - 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 + } + } + self._local['NUM_PEERS'] = len(self.peers) + logger.debug('(%s) Peer Added To Peer List: %s (IPSC now has %s Peers)', _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: + self._peers[_peerid]['STATUS']['KEEP_ALIVES_SENT'] += 1 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)) return + # REQUEST FOR A PEER LIST elif _packettype == PEER_LIST_REQ: logger.debug('(%s) Peer List Received from peer %s', self._network, int_id(_peerid)) 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! + + + + # PACKET IS OF AN UNKNOWN TYPE. LOG IT AND IDENTTIFY IT! else: self.unknown_message(self._network, _packettype, _peerid, data) return