def handle_pcbs_propagation(self): """ Generate a new beacon or gets ready to forward the one received. """ timestamp = int(SCIONTime.get_time()) # Create beacon for downstream ASes. down_iof = InfoOpaqueField.from_values(timestamp, self.addr.isd_as[0]) downstream_pcb = PathSegment.from_values(down_iof) propagated_pcbs = self.propagate_downstream_pcb(downstream_pcb) # Create beacon for core ASes. core_iof = InfoOpaqueField.from_values(timestamp, self.addr.isd_as[0]) core_pcb = PathSegment.from_values(core_iof) propagated = self.propagate_core_pcb(core_pcb) for k, v in propagated.items(): propagated_pcbs[k].extend(v) # Propagate received beacons. A core beacon server can only receive # beacons from other core beacon servers. beacons = [] with self._rev_seg_lock: for ps in self.core_beacons.values(): beacons.extend(ps.get_best_segments()) for pcb in beacons: propagated = self.propagate_core_pcb(pcb) for k, v in propagated.items(): propagated_pcbs[k].extend(v) self._log_propagations(propagated_pcbs)
def _check_remote_ifid(self, pcb: PathSegment) -> Optional[ISD_AS]: Requires(Acc(pcb.State(), 1 / 20)) Ensures(Acc(pcb.State(), 1 / 20)) """ Checkes whether any PCB markings have unset remote IFID values for up/downstream ASes. This can happen during normal startup depending on the timing of PCB propagation vs IFID keep-alives, but should not happen once the infrastructure is settled. Remote IFID is only allowed to be 0 if the corresponding ISD-AS is 0-0. """ asms = pcb.iter_asms() for asm in asms: Invariant(Forall(asms, lambda a: (Acc(a.State(), 1 / 4), []))) pcbms = asm.iter_pcbms() for pcbm in pcbms: Invariant(Forall(pcbms, lambda p: (Acc(p.State(), 1 / 4), []))) if (pcbm.inIA().to_int() and not Unfolding( Acc(pcbm.State(), 1 / 8), Unfolding(Acc(pcbm.p.State(), 1 / 16), pcbm.p.inIF))): return pcbm.inIA() if (pcbm.outIA().to_int() and not Unfolding( Acc(pcbm.State(), 1 / 8), Unfolding(Acc(pcbm.p.State(), 1 / 16), pcbm.p.outIF))): return pcbm.outIA() return None
def check_filters(self, pcb: PathSegment) -> bool: Requires(Acc(pcb.State(), 1 / 10)) Requires(Acc(self.State(), 1 / 7)) Requires(self.valid_ranges()) Ensures(Acc(pcb.State(), 1 / 10)) Ensures(Acc(self.State(), 1 / 7)) """ Runs some checks, including: unwanted ASes and min/max property values. :param pcb: beacon to analyze. :type pcb: :class:`PathSegment` :returns: True if any unwanted AS is present or a range is not respected. :rtype: bool """ assert isinstance(pcb, PathSegment) isd_as = self._check_unwanted_ases(pcb) if isd_as: logging.warning("PathStore: pcb discarded, unwanted AS(%s): %s", isd_as, pcb.short_desc()) return False reasons = self._check_property_ranges(pcb) if reasons: logging.info("PathStore: pcb discarded(%s): %s", ", ".join(reasons), pcb.short_desc()) return False ia = self._check_remote_ifid(pcb) if ia: logging.error( "PathStore: pcb discarded, remote IFID of %s unknown", ) return False return True
def _setup(self): asms = [] for i in range(3): pcbm = create_mock_full({"hof()": "hof %d" % i}) asms.append(create_mock_full({"pcbm()": pcbm})) inst = PathSegment(None) inst.iter_asms = create_mock_full(return_value=asms) return inst
def _setup(self): inst = PathSegment({}) inst.get_all_iftokens = create_mock_full(return_value=("t0", "t1")) h = create_mock_full({ 'update()': None, 'digest()': "digest", 'hexdigest()': "hexdigest" }) return inst, h
def _parse(self, raw): """ Parse payload to extract path. """ data = Raw(raw, self.NAME, self.MIN_LEN, min_=True) super()._parse(data) self.path_type = data.pop(1) if self.path_type == PathTransType.OF_PATH: self.path = PathTransOFPath(data.pop()) elif self.path_type == PathTransType.PCB_PATH: self.path = PathSegment(data.pop()) else: raise SCIONParseError("Unsupported path type: %s", self.path_type)
def test(self, _): asms = [] for i in range(3): pcbms = [] for j in range(2): pcbms.append( create_mock_full({"igRevToken": "ig %d %d" % (i, j)})) asms.append( create_mock_full({ "pcbms": pcbms, "egRevToken": "eg %d" % i })) inst = PathSegment(create_mock_full({"asms": asms})) expected = [ 'ig 0 0', 'ig 0 1', 'eg 0', 'ig 1 0', 'ig 1 1', 'eg 1', 'ig 2 0', 'ig 2 1', 'eg 2' ] # Call ntools.eq_(inst.get_all_iftokens(), expected)
def _handle_pcbs_from_zk(self, pcbs): """ Handles cached pcbs through ZK, passed as a list. """ for pcb in pcbs: try: pcb = PathSegment.from_raw(pcb) except SCIONParseError as e: logging.error("Unable to parse raw pcb: %s", e) continue self.handle_pcb(pcb) logging.debug("Processed %s PCBs from ZK", len(pcbs))
def _check_property_ranges(self, pcb: PathSegment) -> List[str]: Requires(Acc(self.State(), 1 / 100)) Requires(self.valid_ranges()) Ensures(Acc(self.State(), 1 / 100)) Ensures(Acc(list_pred(Result()))) """ Checks whether any of the path properties has a value outside the predefined min-max range. :param pcb: beacon to analyze. :type pcb: :class:`PathSegment` """ reasons = [] # type: List[str] self._check_range(reasons, "PeerLinks", pcb.get_n_peer_links()) self._check_range(reasons, "HopsLength", pcb.get_n_hops()) self._check_range(reasons, "DelayTime", int(SCIONTime.get_time()) - pcb.get_timestamp()) self._check_range(reasons, "GuaranteedBandwidth", 10) self._check_range(reasons, "AvailableBandwidth", 10) self._check_range(reasons, "TotalBandwidth", 10) return reasons
def handle_pcbs_propagation(self): """ Generate a new beacon or gets ready to forward the one received. """ timestamp = int(SCIONTime.get_time()) # Create beacon for downstream ASes. down_iof = InfoOpaqueField.from_values(timestamp, self.addr.isd_as[0]) downstream_pcb = PathSegment.from_values(down_iof) self.propagate_downstream_pcb(downstream_pcb) # Create beacon for core ASes. core_iof = InfoOpaqueField.from_values(timestamp, self.addr.isd_as[0]) core_pcb = PathSegment.from_values(core_iof) core_count = self.propagate_core_pcb(core_pcb) # Propagate received beacons. A core beacon server can only receive # beacons from other core beacon servers. beacons = [] for ps in self.core_beacons.values(): beacons.extend(ps.get_best_segments()) for pcb in beacons: core_count += self.propagate_core_pcb(pcb) if core_count: logging.info("Propagated %d Core PCBs", core_count)
def _check_unwanted_ases( self, pcb: PathSegment) -> Optional[ISD_AS]: # pragma: no cover Requires(Acc(pcb.State(), 1 / 20)) Requires(Acc(self.State(), 1 / 8)) Ensures(Acc(pcb.State(), 1 / 20)) Ensures(Acc(self.State(), 1 / 8)) """ Checks whether any of the ASes in the path belong to the black list. :param pcb: beacon to analyze. :type pcb: :class:`PathSegment` """ asms = pcb.iter_asms() for asm in asms: Invariant(Forall(asms, lambda a: (Acc(a.State(), 1 / 4), []))) Invariant(Acc(self.State(), 1 / 9)) Invariant(Acc(pcb.State(), 1 / 20)) isd_as = asm.isd_as() Unfold(Acc(self.State(), 1 / 10)) if isd_as in self.unwanted_ases: Fold(Acc(self.State(), 1 / 10)) return isd_as Fold(Acc(self.State(), 1 / 10))
def _verify_path_seg(self, seg_meta): """ Signature verification for all AS markings within this pcb/path segment. This function is called, when all TRCs and CCs used within this pcb/path segment are available. """ seg = seg_meta.seg ver_seg = PathSegment.from_values(seg.info) for asm in seg.iter_asms(): cert_ia = asm.isd_as() trc = self.trust_store.get_trc(cert_ia[0], asm.p.trcVer) chain = self.trust_store.get_cert(asm.isd_as(), asm.p.certVer) ver_seg.add_asm(asm) verify_sig_chain_trc(ver_seg.sig_pack3(), asm.p.sig, cert_ia, chain, trc)
def process_pcbs(self, pcbs, raw=True): """ Process new beacons and appends them to beacon list. """ for pcb in pcbs: if raw: try: pcb = PathSegment.from_raw(pcb) except SCIONParseError as e: logging.error("Unable to parse raw pcb: %s", e) continue if self.path_policy.check_filters(pcb): self._try_to_verify_beacon(pcb) self.handle_ext(pcb)
def _verify_path_seg(self, seg_meta): """ Signature verification for all AS markings within this pcb/path segment. This function is called, when all TRCs and CCs used within this pcb/path segment are available. """ seg = seg_meta.seg ver_seg = PathSegment.from_values(seg.info) for asm in seg.iter_asms(): cert_ia = asm.isd_as() trc = self.trust_store.get_trc(cert_ia[0], asm.p.trcVer) chain = self.trust_store.get_cert(asm.isd_as(), asm.p.certVer) ver_seg.add_asm(asm) if not verify_sig_chain_trc(ver_seg.sig_pack3(), asm.p.sig, str(cert_ia), chain, trc, asm.p.trcVer): logging.error("ASM verification failed: %s" % asm.short_desc()) return False return True
def test_3(self, _): asms = [] for i in range(3): asms.append( create_mock_full({"sig_pack()": bytes("asm %i" % i, "ascii")})) inst = PathSegment(create_mock_full({"info": b"info"})) inst.is_sibra = create_mock_full() inst.iter_asms = create_mock_full(return_value=asms) inst.sibra_ext = create_mock_full({"sig_pack()": b"sibraext"}) expected = b"".join( [b"info", b"asm 0", b"asm 1", b"asm 2", b"sibraext"]) # Call ntools.eq_(inst.sig_pack(3), expected)
def process_pcbs(self, pcbs, raw=True): """ Process new beacons and appends them to beacon list. """ count = 0 for pcb in pcbs: if raw: try: pcb = PathSegment.from_raw(pcb) except SCIONParseError as e: logging.error("Unable to parse raw pcb: %s", e) continue if not self._filter_pcb(pcb): count += 1 continue self._try_to_verify_beacon(pcb) self.handle_ext(pcb) if count: logging.debug("Dropped %d looping Core Segment PCBs", count)
def _create_reg_pcb(self, remote): # TODO(kormat): It might make sense to remove peer markings also, but # they might also be needed for sibra steady paths that traverse peer # links in the future. latest = self.blocks[-1] assert latest.num_hops == len(latest.sofs) info = copy.deepcopy(latest.info) info.fwd_dir = not remote sofs = latest.sofs[:] up = True if remote: sofs.reverse() if self.link_type == LinkType.PARENT: up = False pcb_d = self.seg.to_dict() if remote and self.link_type == LinkType.CORE: pcb_d['asms'].reverse() pcb = PathSegment.from_dict(pcb_d) pcb_ext = SibraPCBExt.from_values(self.id, info, sofs, up) pcb.add_sibra_ext(pcb_ext.p) pcb.sign(self.signing_key) logging.debug(self._reg_pcb_str(pcb)) return pcb
def test(self): """ Test the main functionalities of the path store. """ path_policy_file = "topology/ISD1/path_policies/ISD1-AD10.json" path_policy = PathPolicy.from_file(path_policy_file) test_segments = PathStore(path_policy) print("Best paths: " + str(len(test_segments.get_best_segments()))) print("Paths in path store: " + str(len(test_segments.candidates))) print("Paths in latest history snapshot: " + str(len(test_segments.get_latest_history_snapshot())) + "\n") path = 1 for _ in range(1, 6): for _ in range(1, 6): pcb = PathSegment() pcb.iof = InfoOpaqueField.from_values(OFT.TDC_XOVR, False, int(time.time()), path) ad_marking = self._create_ad_marking() pcb.add_ad(ad_marking) print("insert path " + str(path) + ", exp time: " + str(pcb.get_expiration_time())) test_segments.add_segment(pcb) path += 1 print("Best paths: " + str(len(test_segments.get_best_segments()))) print("Paths in path store: " + str(len(test_segments.candidates))) print("Paths in latest history snapshot: " + str(len(test_segments.get_latest_history_snapshot()))) print("Time: " + str(int(time.time())) + "\n") time.sleep(5) print("Waiting for some paths to expire...") time.sleep(25) print("Best paths: " + str(len(test_segments.get_best_segments()))) print("Paths in path store: " + str(len(test_segments.candidates))) print("Paths in latest history snapshot: " + str(len(test_segments.get_latest_history_snapshot())))
class PathTransportExt(EndToEndExtension): """ For path of type OF_PATH, the header is presented below, path is in data-plane format and instantiated as object of PathTransOFPath. 0B 1 2 3 4 5 6 7 +--------+--------+--------+--------+--------+--------+--------+--------+ | xxxxxxxxxxxxxxxxxxxxxxxx |OF_PATH |src_type|dst_type| scion_src_addr | +--------+--------+--------+--------+--------+--------+--------+--------+ | (cont., var len) | scion_dst_addr (var len) | +--------+--------+--------+--------+--------+--------+--------+--------+ | Path (list of opaque fields, var len) + padding (if necessary) | +--------+--------+--------+--------+--------+--------+--------+--------+ For path of type PCB_PATH, the header is presented below, path is in control-plane format and instantiated as object of PathSegment. 0B 1 2 3 4 5 6 7 +--------+--------+--------+--------+--------+--------+--------+--------+ | xxxxxxxxxxxxxxxxxxxxxxxx |PCB_PATH| Path (as PathSegment) | +--------+--------+--------+--------+--------+--------+--------+--------+ | Path (cont., var len) + padding (if necessary) | +--------+--------+--------+--------+--------+--------+--------+--------+ """ NAME = "PathTransportExt" EXT_TYPE = ExtEndToEndType.PATH_TRANSPORT def __init__(self, raw=None): # pragma: no cover self.path_type = None self.path = None super().__init__(raw) @classmethod def from_values(cls, path_type, path): """ Construct extension with a path of type path_type. """ inst = cls() inst.path_type = path_type inst.path = path plen = len(inst.path.pack()) # How many additional lines are needed for a path. inst._init_size(math.ceil((plen - 4) / inst.LINE_LEN)) return inst def _parse(self, raw): """ Parse payload to extract path. """ data = Raw(raw, self.NAME, self.MIN_LEN, min_=True) super()._parse(data) self.path_type = data.pop(1) if self.path_type == PathTransType.OF_PATH: self.path = PathTransOFPath(data.pop()) elif self.path_type == PathTransType.PCB_PATH: self.path = PathSegment(data.pop()) else: raise SCIONParseError("Unsupported path type: %s", self.path_type) def pack(self): packed = [] packed.append(struct.pack("!B", self.path_type)) path_packed = self.path.pack() packed.append(path_packed) # Add possible padding. packed.append(bytes(calc_padding(len(path_packed) - 4, self.LINE_LEN))) raw = b"".join(packed) self._check_len(raw) return raw def __str__(self): # pragma: no cover return "%s(%dB): type: %s\n %s" % (self.NAME, len(self), PathTransType.to_str( self.path_type), self.path)
def iter_pcbs(self): for rec in self.p.recs: yield rec.type, PathSegment(rec.pcb)