def handle_revocation(self, cpld, meta): pmgt = cpld.union rev_info = pmgt.union assert isinstance(rev_info, RevocationInfo), type(rev_info) logging.debug("Revocation info received: %s", rev_info.short_desc()) try: rev_info.validate() except SCIONBaseError as e: logging.warning("Failed to validate RevInfo from %s: %s", meta, e) return SCIONDRevReplyStatus.INVALID # Verify epoch information and on failure return directly epoch_status = ConnectedHashTree.verify_epoch(rev_info.p.epoch) if epoch_status == ConnectedHashTree.EPOCH_PAST: logging.error( "Failed to verify epoch: epoch in the past %d,current epoch %d." % (rev_info.p.epoch, ConnectedHashTree.get_current_epoch())) return SCIONDRevReplyStatus.INVALID if epoch_status == ConnectedHashTree.EPOCH_FUTURE: logging.warning( "Failed to verify epoch: epoch in the future %d,current epoch %d." % (rev_info.p.epoch, ConnectedHashTree.get_current_epoch())) return SCIONDRevReplyStatus.INVALID if epoch_status == ConnectedHashTree.EPOCH_NEAR_PAST: logging.info( "Failed to verify epoch: epoch in the near past %d, current epoch %d." % (rev_info.p.epoch, ConnectedHashTree.get_current_epoch())) return SCIONDRevReplyStatus.STALE self.peer_revs.add(rev_info) # Go through all segment databases and remove affected segments. removed_up = self._remove_revoked_pcbs(self.up_segments, rev_info) removed_core = self._remove_revoked_pcbs(self.core_segments, rev_info) removed_down = self._remove_revoked_pcbs(self.down_segments, rev_info) logging.info("Removed %d UP- %d CORE- and %d DOWN-Segments." % (removed_up, removed_core, removed_down)) total = removed_up + removed_core + removed_down if total > 0: return SCIONDRevReplyStatus.VALID else: # FIXME(scrye): UNKNOWN is returned in the following situations: # - No matching segments exist # - Matching segments exist, but the revoked interface is part of # a peering link; new path queries sent to SCIOND won't use the # link, but nothing is immediately revoked # - A hash check failed and prevented the revocation from taking # place # # This should be fixed in the future to provide clearer meaning # behind why the revocation could not be validated. # # For now, if applications receive an UNKOWN reply to a revocation, # they should strongly consider flushing paths containing the # interface. return SCIONDRevReplyStatus.UNKNOWN
def add_rev_infos(self, rev_infos): # pragma: no cover """ Appends a list of revocations to the PCB. Replaces existing revocations with newer ones. """ if not rev_infos: return existing = {} current_epoch = ConnectedHashTree.get_current_epoch() for i in range(len(self.p.exts.revInfos)): orphan = self.p.exts.revInfos.disown(i) info_p = orphan.get() if info_p.epoch >= current_epoch: existing[(info_p.isdas, info_p.ifID)] = orphan # Remove revocations for which we already have a newer one. filtered = [] for info in rev_infos: if (info.p.epoch >= current_epoch and (info.p.isdas, info.p.ifID) not in existing): filtered.append(info) self.p.exts.init("revInfos", len(existing) + len(filtered)) for i, orphan in enumerate(existing.values()): self.p.exts.revInfos.adopt(i, orphan) n_existing = len(existing) for i, info in enumerate(filtered): self.p.exts.revInfos[n_existing + i] = info.p
def _remove_revoked_pcbs(self, db, rev_info): """ Removes all segments from 'db' that contain an IF token for which rev_token is a preimage (within 20 calls). :param db: The PathSegmentDB. :type db: :class:`lib.path_db.PathSegmentDB` :param rev_info: The revocation info :type rev_info: RevocationInfo :returns: The number of deletions. :rtype: int """ if not ConnectedHashTree.verify_epoch(rev_info.p.epoch): logging.debug( "Failed to verify epoch: rev_info epoch %d,current epoch %d." % (rev_info.p.epoch, ConnectedHashTree.get_current_epoch())) return 0 to_remove = [] for segment in db(full=True): for asm in segment.iter_asms(): if self._verify_revocation_for_asm(rev_info, asm): logging.debug("Removing segment: %s" % segment.short_desc()) to_remove.append(segment.get_hops_hash()) return db.delete_all(to_remove)
def _handle_if_timeouts(self): """ Periodically checks each interface state and issues an if revocation, if no keep-alive message was received for IFID_TOUT. """ if_id_last_revoked = defaultdict(int) while self.run_flag.is_set(): start_time = time.time() with self.ifid_state_lock: to_revoke = [] for (if_id, if_state) in self.ifid_state.items(): cur_epoch = ConnectedHashTree.get_current_epoch() if not if_state.is_expired() or ( if_state.is_revoked() and if_id_last_revoked[if_id] == cur_epoch): # Either the interface hasn't timed out, or it's already revoked for this # epoch continue if_id_last_revoked[if_id] = cur_epoch if not if_state.is_revoked(): logging.info("IF %d went down.", if_id) to_revoke.append(if_id) if_state.revoke_if_expired() self._issue_revocations(to_revoke) sleep_interval(start_time, self.IF_TIMEOUT_INTERVAL, "Handle IF timeouts")
def _add_peer_revs(self, segments): """ Adds revocations to revoked peering interfaces in segments. :returns: Set with modified segments. Elements of the original set stay untouched. """ # TODO(shitz): This could be optimized, by keeping a map of (ISD_AS, IF) # -> RevocationInfo for peer revocations. modified_segs = set() current_epoch = ConnectedHashTree.get_current_epoch() for segment in segments: seg_id = segment.get_hops_hash() with self.pcb_cache_lock: if seg_id in self.pcb_cache: cached_seg = self.pcb_cache[seg_id] logging.debug("Adding segment from PCB cache to response:" " %s" % cached_seg.short_desc()) modified_segs.add(cached_seg) continue revs_to_add = set() for rev_info in list(self.revocations): if rev_info.p.epoch < current_epoch: self.revocations.pop(rev_info) continue for asm in segment.iter_asms(): if asm.isd_as() != rev_info.isd_as(): continue for pcbm in asm.iter_pcbms(1): hof = pcbm.hof() if rev_info.p.ifID in [ hof.ingress_if, hof.egress_if ]: revs_to_add.add(rev_info.copy()) if revs_to_add: new_seg = segment.copy() new_seg.add_rev_infos(list(revs_to_add)) logging.debug("Adding revocations to PCB: %s" % new_seg.short_desc()) self.pcb_cache[seg_id] = new_seg modified_segs.add(new_seg) else: modified_segs.add(segment) return modified_segs
def _handle_if_timeouts(self): """ Periodically checks each interface state and issues an if revocation, if no keep-alive message was received for IFID_TOUT. """ if_id_last_revoked = defaultdict(int) while self.run_flag.is_set(): start_time = time.time() with self.ifid_state_lock: for (if_id, if_state) in self.ifid_state.items(): cur_epoch = ConnectedHashTree.get_current_epoch() # Check if interface has timed-out. if ((if_state.is_expired() or if_state.is_revoked()) and (if_id_last_revoked[if_id] != cur_epoch)): if_id_last_revoked[if_id] = cur_epoch if not if_state.is_revoked(): logging.info("IF %d appears to be down.", if_id) self._issue_revocation(if_id) if_state.revoke_if_expired() sleep_interval(start_time, self.IF_TIMEOUT_INTERVAL, "Handle IF timeouts")