def test_same_epoch(self, time): # Setup time.return_value = HASHTREE_EPOCH_TIME + HASHTREE_EPOCH_TOLERANCE + 1 # Call and tests ntools.eq_(ConnectedHashTree.verify_epoch(1), ConnectedHashTree.EPOCH_OK) ntools.eq_(ConnectedHashTree.verify_epoch(2), ConnectedHashTree.EPOCH_FUTURE)
def test_different_epoch(self, time): # Setup time.return_value = HASHTREE_EPOCH_TIME + 1 # Call and test ntools.eq_(ConnectedHashTree.verify_epoch(0), ConnectedHashTree.EPOCH_OK) ntools.eq_(ConnectedHashTree.verify_epoch(1), ConnectedHashTree.EPOCH_OK)
def _remove_revoked_segments(self, rev_info): """ Try the previous and next hashes as possible astokens, and delete any segment that matches :param rev_info: The revocation info :type rev_info: RevocationInfo """ if ConnectedHashTree.verify_epoch( rev_info.p.epoch) != ConnectedHashTree.EPOCH_OK: return (hash01, hash12) = ConnectedHashTree.get_possible_hashes(rev_info) if_id = rev_info.p.ifID with self.htroot_if2seglock: down_segs_removed = 0 core_segs_removed = 0 up_segs_removed = 0 for h in (hash01, hash12): for sid in self.htroot_if2seg.pop((h, if_id), []): if self.down_segments.delete( sid) == DBResult.ENTRY_DELETED: down_segs_removed += 1 if self.core_segments.delete( sid) == DBResult.ENTRY_DELETED: core_segs_removed += 1 if not self.topology.is_core_as: if (self.up_segments.delete(sid) == DBResult.ENTRY_DELETED): up_segs_removed += 1 logging.debug( "Removed segments revoked by [%s]: UP: %d DOWN: %d CORE: %d" % (rev_info.short_desc(), up_segs_removed, down_segs_removed, core_segs_removed))
def add(self, rev_info): """ Adds rev_info to the cache and returns True if the operation succeeds. """ if not ConnectedHashTree.verify_epoch(rev_info.p.epoch): return False with self._lock: key = _mk_key(rev_info) stored_info = self.get(key) if not stored_info: # Try to free up space in case the cache reaches the cap limit. if len(self._cache) >= self._capacity: for info in list(self._cache.values()): self._validate_entry(info) # Couldn't free up enough space... if len(self._cache) >= self._capacity: logging.error("Revocation cache full!.") return False self._cache[key] = rev_info if self._labels: REVS_ADDED.labels(**self._labels).inc() REVS_TOTAL.labels(**self._labels).inc() REVS_BYTES.labels(**self._labels).inc(len(rev_info)) return True if rev_info.p.epoch > stored_info.p.epoch: self._cache[key] = rev_info if self._labels: REVS_ADDED.labels(**self._labels).inc() REVS_REMOVED.labels(**self._labels).inc() REVS_BYTES.labels(**self._labels).inc(len(rev_info) - len(stored_info)) return True return False
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 _pcb_list_to_remove(self, candidates, rev_info): """ Calculates the list of PCBs to remove. Called by _remove_revoked_pcbs. :param candidates: Candidate PCBs. :type candidates: List :param rev_info: The RevocationInfo object. :type rev_info: RevocationInfo """ to_remove = [] processed = set() for cand in candidates: if cand.id in processed: continue processed.add(cand.id) if not ConnectedHashTree.verify_epoch(rev_info.p.epoch): continue # If the interface on which we received the PCB is # revoked, then the corresponding pcb needs to be removed. root_verify = ConnectedHashTree.verify(rev_info, self._get_ht_root()) if (self.addr.isd_as == rev_info.isd_as() and cand.pcb.p.ifID == rev_info.p.ifID and root_verify): to_remove.append(cand.id) for asm in cand.pcb.iter_asms(): if self._verify_revocation_for_asm(rev_info, asm, False): to_remove.append(cand.id) return to_remove
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 _validate_entry(self, rev_info, cur_epoch=None): # pragma: no cover """Removes an expired revocation from the cache.""" if not ConnectedHashTree.verify_epoch(rev_info.p.epoch, cur_epoch): del self._cache[_mk_key(rev_info)] if self._labels: REVS_REMOVED.labels(**self._labels).inc() REVS_TOTAL.labels(**self._labels).dec() REVS_BYTES.labels(**self._labels).dec(len(rev_info)) return False return True
def _validate_segment(self, seg): """ Check segment for revoked upstream/downstream interfaces. :param seg: The PathSegment object. :return: False, if the path segment contains a revoked upstream/ downstream interface (not peer). True otherwise. """ for rev_info in list(self.revocations): if not ConnectedHashTree.verify_epoch(rev_info.p.epoch): self.revocations.pop(rev_info) continue for asm in seg.iter_asms(): pcbm = asm.pcbm(0) if (rev_info.isd_as() == asm.isd_as() and rev_info.p.ifID in [pcbm.p.inIF, pcbm.p.outIF]): logging.debug("Found revoked interface (%d) in segment " "%s." % (rev_info.p.ifID, seg.short_desc())) return False return True
def _skip_peer(cls, peer_rev, ht_root): # pragma: no cover if not peer_rev: return False return (ConnectedHashTree.verify_epoch(peer_rev.p.epoch) and ConnectedHashTree.verify(peer_rev, ht_root))
def _skip_peer(peer_rev, ht_root): # pragma: no cover if not peer_rev: return False rev_status = ConnectedHashTree.verify_epoch(peer_rev.p.epoch) return (rev_status == ConnectedHashTree.EPOCH_OK and ConnectedHashTree.verify(peer_rev, ht_root))
def _validate_entry(self, rev_info, cur_epoch=None): # pragma: no cover """Removes an expired revocation from the cache.""" if not ConnectedHashTree.verify_epoch(rev_info.p.epoch, cur_epoch): del self._cache[_mk_key(rev_info)] return False return True