def print_alert(alert): try: alert_type = AlertType.find_one_by_code(alert.type) message= alert_type.code + '-' + alert_type.message dict_parameters= json.loads(alert.parameters) for param_name, param_value in dict_parameters.items(): message= message.replace('{'+param_name+'}', str(param_value)) message= message.replace('{'+'packet_id'+'}', str(alert.packet_id)) message= message.replace('{'+'created_at'+'}', alert.created_at.strftime('%Y-%m-%d %H:%M')) collector= DataCollector.get(alert.data_collector_id) if collector: message= message.replace('{'+'collector.name'+'}', collector.name+' (ID '+str(collector.id)+')') logging.debug(message) except Exception as e: logging.error('Error printing alert: {0}'.format(e))
def gateway_resource_usage(self, gateway, packet, policy_manager): gateway.npackets_up += 1 if packet.uplink else 0 gateway.npackets_down += 1 if not packet.uplink else 0 gateway.last_activity = packet.date # If gateway is reconnecting, then resolve every "not transmitting" # issue for this gateway, with reason_id 0 (problem solved automatically) if not gateway.connected: res_comment = "The gateway has transmitted again" issue_solved = Issue.solve( resolution_reason=res_comment, date=packet.date, issue_type="LAF-403", gateway_id=gateway.id, ) if issue_solved: emit_alert( "LAF-600", packet, gateway=gateway, alert_solved_type="LAF-403", alert_solved=AlertType.find_one_by_code("LAF-403").name, resolution_reason=res_comment) gateway.connected = True # Update activity_freq (which is the time between packets) time_diff = (packet.date - self.gateway_stats[gateway.id]["last_date"]).seconds if gateway.activity_freq: maw = policy_manager.get_parameters( "LAF-102")["moving_average_weight"] gateway.activity_freq = maw * gateway.activity_freq + ( 1 - maw) * time_diff else: gateway.activity_freq = time_diff
def process_packet(packet, policy): result = "" key_tested = False global device_auth_obj global dontGenerateKeys global keys global hours_betweeen_bruteforce_trials device_auth_obj = None if packet.m_type not in ("JoinRequest", "JoinAccept"): return if not policy.is_enabled("LAF-009"): return if packet.dev_eui is None: return if packet.m_type == "JoinRequest": # device_obj = ObjectInstantiator.get_or_error_device(packet) device_obj = Device.find_with( dev_eui=packet.dev_eui, data_collector_id=packet.data_collector_id) if not device_obj: return # Before cracking with many different keys, try with a PotentialAppKey previously found. In case this key is valid, we are pretty sure that is the correct AppKey device_auth_obj = DeviceAuthData.find_one_by_device_id(device_obj.id) if device_auth_obj and extractMIC( device_auth_obj.join_request) != packet.mic: pot_app_keys = [ pk.app_key_hex for pk in PotentialAppKey.find_all_by_device_auth_id(device_auth_obj.id) ] if len(pot_app_keys) > 0: correct_app_keys = LorawanWrapper.testAppKeysWithJoinRequest( keys=[bytes(pk, encoding='utf-8') for pk in pot_app_keys], data=packet.data, dontGenerateKeys=True).split() correct_app_keys = [ ak.upper().rstrip() for ak in correct_app_keys ] key_tested = True pk_to_remove = [ pk for pk in pot_app_keys if pk not in correct_app_keys ] PotentialAppKey.delete_keys( device_auth_data_id=device_auth_obj.id, keys=pk_to_remove) if len(correct_app_keys) > 1: logging.warning( f"Found more than one possible keys for the device {packet.dev_eui}." + f" One of them should be the correct. Check it manually. Keys: {correct_app_keys}" ) elif len(correct_app_keys) == 1: # AppKey found!! device_auth_obj.second_join_request_packet_id = packet.id device_auth_obj.second_join_request = packet.data device_auth_obj.app_key_hex = correct_app_keys[0] emit_alert( "LAF-009", packet, device=device_obj, device_auth_id=device_auth_obj.id, app_key=correct_app_keys[0], packet_id_1=device_auth_obj.join_request_packet_id, packet_type_1="JoinRequest", packet_type_2="JoinRequest") return # Check if the DeviceAuthData wasn't already generated never_bruteforced = False if device_auth_obj is None: never_bruteforced = True try: device_auth_obj = DeviceAuthData( device_id=device_obj.id, data_collector_id=packet.data_collector_id, organization_id=packet.organization_id, join_request=packet.data, created_at=packet.date, join_request_packet_id=packet.id) device_auth_obj.save() except Exception as exc: logging.error( "Error trying to save DeviceAuthData at JoinRequest: {0}". format(exc)) # Check when was the last time it was bruteforced and # Try checking with the keys dictionary, the keys generated on the fly # and the keys uploaded by the corresponding organization elapsed = packet.date - device_auth_obj.created_at # Time in seconds if elapsed > datetime.timedelta(hours=hours_betweeen_bruteforce_trials ) or never_bruteforced or True: result = LorawanWrapper.testAppKeysWithJoinRequest( keys, packet.data, dontGenerateKeys) organization_keys = [ bytes(app_key.key.upper(), encoding='utf-8') for app_key in AppKey.get_with( organization_id=packet.organization_id) ] result_org_keys = LorawanWrapper.testAppKeysWithJoinRequest( organization_keys, packet.data, dontGenerateKeys=True) if result_org_keys != "": result += " " + result_org_keys key_tested = True # Update the last time it was bruteforced device_auth_obj.created_at = packet.date # If potential keys found... if result != "": device_auth_obj.join_request_packet_id = packet.id device_auth_obj.join_request = packet.data # Split string possibly containing keys separated by spaces candidate_keys_array = set(result.split()) for hex_key in candidate_keys_array: try: # Save the potential app key if it does not exists already in the DB potential_key_obj = PotentialAppKey.get_by_device_auth_data_and_hex_app_key( device_auth_data_id=device_auth_obj.id, app_key_hex=hex_key.upper()) if not potential_key_obj: potential_key_obj = PotentialAppKey( app_key_hex=hex_key.upper(), organization_id=packet.organization_id, last_seen=packet.date, packet_id=packet.id, device_auth_data_id=device_auth_obj.id) potential_key_obj.save() except Exception as exc: logging.error( "Error trying to save PotentialAppKey at JoinRequest: {0}" .format(exc)) elif packet.m_type == "JoinAccept" and packet.data is not None: last_seconds_date = packet.date - datetime.timedelta(seconds=5) try: organization_keys = PotentialAppKey.find_all_by_organization_id_after_datetime( packet.organization_id, last_seconds_date) # Return if no JR keys were found if len(organization_keys) != 0: keys_array = list() for pk in organization_keys: # Fetch keys in byte format. Needed by ctypes keys_array.append( bytes(pk.app_key_hex.rstrip().upper(), encoding='utf-8')) # Remove possible duplicates in array keys_array = list(dict.fromkeys(keys_array)) result = LorawanWrapper.testAppKeysWithJoinAccept( keys_array, packet.data, True) key_tested = True except Exception as es: logging.error(f"Error trying to bforce JA: {es}") if result != "": # Clean the key string result = result.rstrip().upper() for potential_key_obj in organization_keys: if potential_key_obj.app_key_hex.upper() == result: device_auth_obj = DeviceAuthData.find_one_by_id( potential_key_obj.device_auth_data_id) break if device_auth_obj: #Add missing data device_auth_obj.join_accept = packet.data device_auth_obj.join_accept_packet_id = packet.id device_auth_obj.app_key_hex = result # Add session keys device_auth_obj = deriveSessionKeys(device_auth_obj, result) # Get the device to get dev_eui device_obj = Device.get(device_auth_obj.device_id) # Get DevAddr from JA packet dev_addr = LorawanWrapper.getDevAddr(result, packet.data) emit_alert("LAF-009", packet, device=device_obj, device_auth_id=device_auth_obj.id, app_key=result, dev_addr=dev_addr, packet_id_1=device_auth_obj.join_request_packet_id, packet_type_1="JoinRequest", packet_type_2="JoinAccept") else: logging.error( "Cracked a JoinAccept but no device_auth object found") if key_tested and len(result) == 0: res_comment = "The AppKey was modified" issue_solved = Issue.solve( resolution_reason=res_comment, date=packet.date, issue_type="LAF-009", device_id=device_obj.id, ) if issue_solved: emit_alert("LAF-600", packet, device=device_obj, alert_solved_type="LAF-009", alert_solved=AlertType.find_one_by_code("LAF-009").name, resolution_reason=res_comment)
def __call__(self, packet, device_session, device, gateway, policy): if self.last_gc is None: self.last_gc = packet.date if (packet.date - self.last_gc).seconds > 3600: self.garbage_collection(today=packet.date) if (device is None or gateway is None or packet.f_count is None or not device.is_otaa): return # Can't be done anything without these data or with an ABP device # Used to identify a "connection" to check. Here for connection we are # talking about the flow of packets between a gateway and a device # in the context of a stablished device_session. lpacket_uid = (device.id, gateway.id) if lpacket_uid not in self.last_packet: # first packet for this device self.last_packet[lpacket_uid] = { 'f_count': packet.f_count, 'dev_addr': packet.dev_addr, 'has_joined': packet.m_type in ["JoinRequest", "JoinAccept"], 'date': packet.date } return if ( # The device has regenerated the session packet.m_type in ["JoinRequest", "JoinAccept"] or (packet.dev_addr is not None and self.last_packet[lpacket_uid]["dev_addr"] != packet.dev_addr)): res_comment = "The device has regenerated the session" issue_solved = Issue.solve( resolution_reason=res_comment, date=packet.date, issue_type="LAF-011", device_id=device.id, ) if issue_solved: emit_alert( "LAF-600", packet, device=device, alert_solved_type="LAF-011", alert_solved=AlertType.find_one_by_code("LAF-011").name, resolution_reason=res_comment) self.last_packet[lpacket_uid]["has_joined"] = True return if (packet.m_type not in ["UnconfirmedDataUp", "ConfirmedDataUp"] or packet.dev_addr is None): return # The packet is not important for the rest of this check if ( # The counter has restarted lpacket_uid in self.last_packet and packet.f_count == 0 and self.last_packet[lpacket_uid]["f_count"] > 65500): if ( # It hasn't regenerated the session not self.last_packet[lpacket_uid]["has_joined"] and policy.is_enabled("LAF-011")): emit_alert("LAF-011", packet, device=device, device_session=device_session, gateway=gateway, counter=device_session.up_link_counter, new_counter=packet.f_count, prev_packet_id=device_session.last_packet_id) # After the restart the join request was "used", for another restart # we need another JR. Therefore, the flag is set to false. self.last_packet[lpacket_uid]["has_joined"] = False self.last_packet[lpacket_uid]['f_count'] = packet.f_count self.last_packet[lpacket_uid]['dev_addr'] = packet.dev_addr self.last_packet[lpacket_uid]['date'] = packet.date
def device_resource_usage(self, device, packet, policy_manager): if packet.uplink and packet.f_count == self.device_stats[ device.id]["last_fcount"]: if ( packet.gateway in self.device_stats[device.id]['last_fcount_gtw'] and \ packet.f_count == self.device_stats[device.id]['last_fcount_gtw'][packet.gateway] ): packet.is_retransmission = True else: # Same packet came from another gateway. Not adding it to last_fcount_gtw # in order to count retransmissions of this packet only in one gateway packet.is_repeated = True return # Repeated or retransmitted uplink packet if not packet.uplink and packet.f_count == self.device_stats[ device.id]["last_fcount_down"]: packet.is_repeated = True return # Repeated downlink packet device.npackets_up += 1 if packet.uplink else 0 device.npackets_down += 1 if not packet.uplink else 0 if packet.uplink and self.device_stats[device.id]["last_fcount"]: device.last_activity = packet.date # If device is reconnecting, then resolve every "not transmitting" # issue for this device, with reason_id 0 (problem solved automatically) if not device.connected: res_comment = "The device has transmitted again" issue_solved = Issue.solve( resolution_reason=res_comment, date=packet.date, issue_type="LAF-401", device_id=device.id, ) if issue_solved: emit_alert( "LAF-600", packet, device=device, alert_solved_type="LAF-401", alert_solved=AlertType.find_one_by_code( "LAF-401").name, resolution_reason="The device has transmitted again") device.connected = True last_fcount = self.device_stats[device.id]["last_fcount"] count_diff = int(packet.f_count - last_fcount) % (2**16) # The counter changed a lot, probably the session was restarted if count_diff > 64 or count_diff < 1: del self.device_stats[device.id] return False # Update activity_freq (which is the time between packets) and activity_freq_variance most_recent_date = ( max(self.device_stats[device.id]["last_date"].values()) ) if self.device_stats[device.id].get("last_date") else packet.date time_diff = (packet.date - most_recent_date).seconds / count_diff if device.activity_freq: freq_diff = time_diff - device.activity_freq maw = policy_manager.get_parameters( "LAF-401")["moving_average_weight"] device.activity_freq = device.activity_freq + (1 - maw) * freq_diff device.activity_freq_variance = maw * device.activity_freq_variance + ( 1 - maw) * (freq_diff**2) else: device.activity_freq = time_diff device.activity_freq_variance = 0 # Update the number of packets lost packet.npackets_lost_found = count_diff - 1 # TODO: this stat update should be removed, after remove it form the back end if device.npackets_lost: maw = 0.9 device.npackets_lost = maw * device.npackets_lost + ( 1 - maw) * (count_diff - 1) else: device.npackets_lost = count_diff - 1 # Update the max (from all the gateways) rssi of the device if packet.rssi is not None: if packet.gateway in self.device_stats[device.id]["rssi"]: maw = policy_manager.get_parameters( "LAF-100")["moving_average_weight"] self.device_stats[device.id]["rssi"][packet.gateway] = \ maw * self.device_stats[device.id]["rssi"][packet.gateway] + \ (1 - maw) * packet.rssi else: self.device_stats[device.id]["rssi"][ packet.gateway] = packet.rssi try: device.max_rssi = max( self.device_stats[device.id]["rssi"].values()) except: pass # Update the max lsnr value of the device, considering all the gateways if packet.lsnr is not None: if packet.gateway in self.device_stats[device.id]["lsnr"]: maw = policy_manager.get_parameters( "LAF-102")["moving_average_weight"] self.device_stats[device.id]["lsnr"][packet.gateway] = \ maw * self.device_stats[device.id]["lsnr"][packet.gateway] + \ (1 - maw) * packet.lsnr else: self.device_stats[device.id]["lsnr"][ packet.gateway] = packet.lsnr try: device.max_lsnr = max( self.device_stats[device.id]["lsnr"].values()) except: pass # Update size of payload. The payload size is in bytes. device.payload_size = self.get_len_bytes_base_64(packet.data) return True
def __call__(self, packet, device_session, device, gateway, policy): if self.last_gc is None: self.last_gc = packet.date if (packet.date - self.last_gc).seconds > 3600: self.garbage_collection(today=packet.date) if (device is None or gateway is None or packet.f_count is None): return # Can't be done anything without these data. if device.is_otaa: return # It's already detected as OTAA, nothing to do. # Used to identify a "connection" to check. Here for connection we are # talking about the flow of packets between a gateway and a device in # in the context of a stablished device_session. lpacket_uid = (device.id, gateway.id) if ( # This indicates that the device is OTAA packet.m_type in ["JoinRequest", "JoinAccept"] or (packet.dev_addr is not None and lpacket_uid in self.last_packet and self.last_packet[lpacket_uid]["dev_addr"] != packet.dev_addr)): res_comment = "The device has sent a join request" issue_solved = Issue.solve( resolution_reason=res_comment, date=packet.date, issue_type="LAF-006", device_id=device.id, ) if issue_solved: emit_alert( "LAF-600", packet, device=device, alert_solved_type="LAF-006", alert_solved=AlertType.find_one_by_code("LAF-006").name, resolution_reason=res_comment) device.is_otaa = True return if packet.m_type in ["UnconfirmedDataUp", "ConfirmedDataUp" ] and packet.dev_addr is not None: if ( # This indicates that the device is ABP lpacket_uid in self.last_packet and policy.is_enabled("LAF-006") and packet.f_count == 0 and self.last_packet[lpacket_uid]["f_count"] > 0 and self.last_packet[lpacket_uid]["dev_addr"] == packet.dev_addr): emit_alert("LAF-006", packet, device=device, device_session=device_session, gateway=gateway, counter=self.last_packet[lpacket_uid]["f_count"], new_counter=packet.f_count) device.is_otaa = False # For each "connection" (see definition in previous comment), we save # the counter and dev_addr. self.last_packet[lpacket_uid] = { 'f_count': packet.f_count, 'dev_addr': packet.dev_addr, 'date': packet.date }
def process_packet(packet, policy): chrono.start("total") chrono.start("dev id") packet = device_identifier(packet) chrono.stop() chrono.start("search objs") gateway = Gateway.find_with(gw_hex_id=packet.gateway, data_collector_id=packet.data_collector_id) device = Device.find_with(dev_eui=packet.dev_eui, data_collector_id=packet.data_collector_id) device_session = DeviceSession.find_with( dev_addr=packet.dev_addr, data_collector_id=packet.data_collector_id) chrono.stop() chrono.start("instantiation") ## Gateway instantiation if gateway is None and packet.gateway: gateway = Gateway.create_from_packet(packet) gateway.save() if policy.is_enabled("LAF-402"): emit_alert("LAF-402", packet, gateway=gateway) ## Device instantiation if device is None and packet.dev_eui: device = Device.create_from_packet(packet) device.save() ## Session instantiation if device_session is None and packet.dev_addr: device_session = DeviceSession.create_from_packet(packet) device_session.save() if device: issue_solved = Issue.solve( resolution_reason="Device connected", date=packet.date, issue_type="LAF-404", device_id=device.id, ) if issue_solved: emit_alert( "LAF-600", packet, device=device, alert_solved_type="LAF-404", alert_solved=AlertType.find_one_by_code("LAF-404").name, resolution_reason="Device connected") ## Emit new device alert if it is the first data packet if device and device_session and device.pending_first_connection: device.pending_first_connection = False session.commit() if policy.is_enabled("LAF-400"): emit_alert("LAF-400", packet, device=device, gateway=gateway, device_session=device_session, number_of_devices=DataCollector.number_of_devices( packet.data_collector_id)) chrono.stop() ## Associations chrono.start("gw2dev") if device and gateway: GatewayToDevice.associate(gateway.id, device.id) chrono.stop() ## Associate device with device_session chrono.start("dev2sess") if device and device_session: if device_session.device_id is not None and device.id != device_session.device_id and policy.is_enabled( "LAF-002"): conflict_device_obj = Device.get(device_session.device_id) emit_alert("LAF-002", packet, device=device, device_session=device_session, gateway=gateway, old_dev_eui=conflict_device_obj.dev_eui, new_dev_eui=device.dev_eui, prev_packet_id=device_session.last_packet_id) if device_session.device_id is None or device.id != device_session.device_id: device_session.device_id = device.id session.commit() chrono.stop() chrono.start("guesses") ## If the packet does not have a gateway, try to guess from the device if gateway is None and device: possible_gateways = GatewayToDevice.associated_with(device.id) if len(possible_gateways) == 1: gateway = Gateway.get(possible_gateways[0]) ## If the packet does not have a dev_eui, try to guess from the device_session if device is None and device_session and device_session.device_id: device = Device.get(device_session.device_id) chrono.stop() chrono.start("checks") ## Check alert ## LAF-404 # The data_collector and dev_eui is used as UID to count JRs. if packet.dev_eui: if packet.m_type == 'JoinRequest': # If the previos packet was also a JR, then it is considered as failed if jr_counters[(packet.data_collector_id, packet.dev_eui)] > 0: packet.failed_jr_found = True jr_counters[(packet.data_collector_id, packet.dev_eui)] += 1 else: jr_counters[(packet.data_collector_id, packet.dev_eui)] = 0 if (policy.is_enabled("LAF-404") and jr_counters[(packet.data_collector_id, packet.dev_eui)] > policy.get_parameters("LAF-404")["max_join_request_fails"]): if device: emit_alert("LAF-404", packet, device=device, gateway=gateway) else: log.warning( f"Device not found in DB when LAF-404 detected for device {packet.dev_eui} from data collector {packet.data_collector_id}" ) ## Check alert LAF-010 if gateway and policy.is_enabled("LAF-010"): location_accuracy = policy.get_parameters( "LAF-010")["location_accuracy"] location_change = gateway.distance_to(packet.latitude, packet.longitude) if location_change > location_accuracy: emit_alert("LAF-010", packet, gateway=gateway, old_latitude=gateway.location_latitude, old_longitude=gateway.location_longitude, new_latitude=packet.latitude, new_longitude=packet.longitude) if packet.m_type == "JoinRequest" and device: # Check if DevNonce is repeated and save it prev_packet_id = DevNonce.saveIfNotExists(packet.dev_nonce, device.id, packet.id) if prev_packet_id and device.has_joined: device.repeated_dev_nonce = True if policy.is_enabled("LAF-001"): emit_alert("LAF-001", packet, device=device, gateway=gateway, dev_nonce=packet.dev_nonce, prev_packet_id=prev_packet_id) elif not (prev_packet_id): device.has_joined = False is_uplink_packet = packet.m_type in [ "UnconfirmedDataUp", "ConfirmedDataUp" ] if device_session and is_uplink_packet and packet.f_count is not None: # Check counter if packet.f_count == 0: # Make sure we have processed at least one packet for this device in this run before firing the alarm if device_session.id in last_uplink_mic: # Skip if received the same counter as previous packet and mics are equal if not (packet.f_count == device_session.up_link_counter and last_uplink_mic[device_session.id] == packet.mic): if device and device.has_joined: # The counter = 0 is valid, then change the has_joined flag device.has_joined = False device_session.reset_counter += 1 session.commit() last_uplink_mic[device_session.id] = packet.mic # Check alert LAF-007 check_duplicated_session(packet=packet, device_session=device_session, device=device, gateway=gateway, policy=policy) # CHeck alert LAF-011 check_session_regeneration(packet=packet, device_session=device_session, device=device, gateway=gateway, policy=policy) # Check alert LAF-006 abp_detector(packet=packet, device_session=device_session, device=device, gateway=gateway, policy=policy) # Check alert LAF-103 check_retransmissions(packet=packet, device_session=device_session, device=device, gateway=gateway, policy_manager=policy) # Check alert LAF-101 check_packets_lost(packet=packet, device_session=device_session, device=device, gateway=gateway, policy_manager=policy) chrono.stop() chrono.start("update") resource_meter(device, packet, policy) resource_meter(gateway, packet, policy) resource_meter.gc(packet.date) if gateway: gateway.update_state(packet) if device_session: device_session.update_state(packet) if device: device.update_state(packet) ## Check alert LAF-100 if ( device and device.max_rssi is not None and \ policy.is_enabled("LAF-100") and \ device.max_rssi < policy.get_parameters("LAF-100")["minimum_rssi"] ): emit_alert("LAF-100", packet, device=device, device_session=device_session, gateway=gateway, rssi=device.max_rssi) ## Check alert LAF-102 if( device and \ device.max_lsnr is not None and \ policy.is_enabled("LAF-102") and \ device.max_lsnr < policy.get_parameters("LAF-102")["minimum_lsnr"] ): emit_alert(alert_type="LAF-102", packet=packet, device=device, device_session=device_session, gateway=gateway, lsnr=device.max_lsnr) chrono.stop("update") chrono.stop("total") chrono.lap()