def _parse(self, raw): """ Parses the raw data and populates the fields accordingly. """ data = Raw(raw, self.NAME, self.LEN) (types, self.total_len, self.hdr_len, iof_off, hof_off, self.next_hdr) = struct.unpack("!HHBBBB", data.pop()) self.version = types >> 12 if self.version != SCION_PROTO_VERSION: raise SCMPBadVersion("Unsupported SCION version: %s" % self.version) self.dst_addr_type = (types >> 6) & 0x3f self.src_addr_type = types & 0x3f self.addrs_len, _ = SCIONAddrHdr.calc_lens( self.dst_addr_type, self.src_addr_type) if self.hdr_len_bytes() < self.LEN + self.addrs_len: # Can't send an SCMP error, as there isn't enough information to # parse the path and the l4 header. raise SCIONParseError( "hdr_len (%sB) < common header len (%sB) + addrs len (%sB) " % (self.hdr_len_bytes(), self.LEN, self.addrs_len)) if iof_off == hof_off == 0: self._iof_idx = self._hof_idx = 0 return if iof_off == 0 or hof_off <= iof_off: raise SCIONParseError( "invalid CurrINF, CurrHF combination: (%s, %s) " % (iof_off, hof_off)) first_of_offset = self.LEN + self.addrs_len # FIXME(kormat): NB this assumes that all OFs have the same length. self._iof_idx = (iof_off * LINE_LEN - first_of_offset) // OpaqueField.LEN self._hof_idx = (hof_off * LINE_LEN - first_of_offset) // OpaqueField.LEN
def _parse_isd_str(self, raw): try: self._isd = int(raw) except ValueError: raise SCIONParseError("Unable to parse ISD from string: %s" % raw) from None if self._isd > self.MAX_ISD: raise SCIONParseError("ISD too large (max: %d): %s" % (self.MAX_ISD, raw))
def _parse_dec_as(self, raw): try: self._as = int(raw, base=10) except ValueError: raise SCIONParseError( "Unable to parse decimal AS from string: %s" % raw) from None if self._as > self.MAX_BGP_AS: raise SCIONParseError("Decimal AS too large (max: %d): %s" % (self.MAX_BGP_AS, raw))
def from_raw(cls, raw): data = Raw(raw, "%s.from_raw" % cls.NAME) plen = struct.unpack("!I", data.pop(4))[0] if len(data) != plen: raise SCIONParseError("Payload length mismatch. Expected: %s Actual: %s" % (plen, len(data))) try: p = cls.P_CLS.from_bytes_packed(data.pop()).as_builder() except capnp.lib.capnp.KjException as e: raise SCIONParseError("Unable to parse %s capnp message: %s" % (cls.NAME, e)) from None return cls.from_proto(p)
def _parse_str(self, raw): """ :param str raw: a string of the format "isd-as". """ isd, as_ = raw.split("-", 1) try: self._isd = int(isd) except ValueError: raise SCIONParseError("Unable to parse ISD from string: %s", raw) try: self._as = int(as_) except ValueError: raise SCIONParseError("Unable to parse AS from string: %s", raw)
def _parse_path(self, data): count = self.cmn_hdr.hdr_len - data.offset() if count < 0: raise SCIONParseError( "Bad header len field (%sB), implies negative path length" % self.cmn_hdr.hdr_len, ) if count > len(data): raise SCIONParseError( "Bad header len field (%sB), " "implies path is longer than packet (%sB)" % (self.cmn_hdr.hdr_len, len(data) + data.offset())) self.path = parse_path(data.get(count)) data.pop(len(self.path)) iof_idx, hof_idx = self.cmn_hdr.get_of_idxs() self.path.set_of_idxs(iof_idx, hof_idx)
def _parse(self, raw): """ Parses the raw data and populates the fields accordingly. """ data = Raw(raw, self.NAME, self.LEN) (types, self.total_len, curr_iof_p, curr_hof_p, self.next_hdr, self.hdr_len) = struct.unpack("!HHBBBB", data.pop()) self.version = types >> 12 if self.version != SCION_PROTO_VERSION: raise SCMPBadVersion("Unsupported SCION version: %s" % self.version) self.src_addr_type = (types & 0x0fc0) >> 6 self.dst_addr_type = types & 0x003f self.addrs_len, _ = SCIONAddrHdr.calc_lens(self.src_addr_type, self.dst_addr_type) if self.hdr_len < self.LEN + self.addrs_len: # Can't send an SCMP error, as there isn't enough information to # parse the path and the l4 header. raise SCIONParseError( "hdr_len (%sB) < common header len (%sB) + addrs len (%sB)" % (self.hdr_len, self.LEN, self.addrs_len)) first_of_offset = self.LEN + self.addrs_len # FIXME(kormat): NB this assumes that all OFs have the same length. self._iof_idx = (curr_iof_p - first_of_offset) // OpaqueField.LEN self._hof_idx = (curr_hof_p - first_of_offset) // OpaqueField.LEN
def _gen_as_certs(self, topo_id, as_conf): # Self-signed if cert_issuer is missing. issuer = TopoID(as_conf.get('cert_issuer', str(topo_id))) # Make sure that issuer is a core AS if issuer not in self.pub_online_root_keys: raise SCIONParseError("Certificate issuer is not a core AS: %s" % issuer) # Create core AS certificate if self.is_core(as_conf): signing_key = self.priv_online_root_keys[topo_id] can_issue = True comment = "Core AS Certificate" self.core_certs[topo_id] = Certificate.from_values( str(topo_id), str(issuer), INITIAL_TRC_VERSION, INITIAL_CERT_VERSION, comment, can_issue, DEFAULT_CORE_CERT_VALIDITY, self.enc_pub_keys[topo_id], self.pub_core_sig_keys[topo_id], signing_key ) # Create regular AS certificate signing_key = self.priv_core_sig_keys[issuer] can_issue = False comment = "AS Certificate" self.certs[topo_id] = Certificate.from_values( str(topo_id), str(issuer), INITIAL_TRC_VERSION, INITIAL_CERT_VERSION, comment, can_issue, DEFAULT_LEAF_CERT_VALIDITY, self.enc_pub_keys[topo_id], self.sig_pub_keys[topo_id], signing_key, issuing_time=int(time.time())+2, )
def from_raw(cls, raw): try: p = cls.P_CLS.from_bytes_packed(raw).as_builder() except capnp.lib.capnp.KjException as e: raise SCIONParseError("Unable to parse %s capnp message: %s" % (cls.NAME, e)) from None return cls.from_proto(p)
def _parse_pld_ctrl(self, data): plen = struct.unpack("!I", data.pop(4))[0] if len(data) != plen: raise SCIONParseError( "Payload length mismatch. Reported: %s Actual: %s" % (plen, len(data))) return msg_from_raw(data.pop())
def __init__(self, trc_dict): """ :param dict trc_dict: TRC as dict. """ for k, (name, type_) in self.FIELDS_MAP.items(): val = trc_dict[k] if type_ in (int, ): val = int(val) elif type_ in (dict, ): val = copy.deepcopy(val) setattr(self, name, val) for attr, decode_list in self.MULTI_DICT_DECODE_FIELDS.items(): field = getattr(self, self.FIELDS_MAP[attr][0]) for entry in field.values(): for key in decode_list: entry[key] = base64.b64decode(entry[key].encode('utf-8')) for attr, decode_list in self.SIMPLE_DICT_DECODE_FIELDS.items(): entry = getattr(self, self.FIELDS_MAP[attr][0]) if not entry: continue for key in decode_list or entry: entry[key] = base64.b64decode(entry[key].encode('utf-8')) for subject, entry in trc_dict[CERT_LOGS_STRING].items(): try: addr, pub_key = next(iter(entry.items())) self.cert_logs[subject][addr] = base64.b64decode( pub_key.encode('utf-8')) except StopIteration: raise SCIONParseError("Invalid CertLogs entry for %s: %s", subject, entry)
def parse_sciond_msg(raw): # pragma: no cover wrapper = P.SCIONDMsg.from_bytes_packed(raw).as_builder() type_ = wrapper.which() for cls_ in _MSG_TYPES: if cls_.MSG_TYPE == type_: return cls_(getattr(wrapper, type_), wrapper.id) raise SCIONParseError("Unsupported SCIOND message type: %s" % type_)
def _parse(self, raw: bytes) -> None: try: decoded = raw.decode("utf-8") except UnicodeDecodeError as e: raise SCIONParseError(e) from None groups = self.FMT_RE.findall(decoded) if not groups: raise SCIONParseError("Input does not match pattern. Decoded: %s" % decoded) from None try: self.ia = ISD_AS(groups[0][0]) except SCIONParseError as e: raise SCIONParseError("Unable to parse IA. Decoded: %s error: %s" % (decoded, e)) from None self.chain_ver = int(groups[0][1]) self.trc_ver = int(groups[0][2])
def from_raw(cls, raw): assert isinstance(raw, bytes), type(raw) try: return cls(cls.P_CLS.from_bytes_packed(raw).as_builder()) except capnp.lib.capnp.KjException as e: raise SCIONParseError("Unable to parse %s capnp message: %s" % (cls, e)) from None
def parse_sciond_msg(raw): # pragma: no cover wrapper = P.SCIONDMsg.from_bytes_packed(raw).as_builder() type_ = wrapper.which() for cls_ in (SCIONDPathReply, SCIONDPathRequest, SCIONDRevNotification, SCIONDASInfoRequest, SCIONDASInfoReply): if cls_.MSG_TYPE == type_: return cls_(getattr(wrapper, type_)) raise SCIONParseError("Unsupported SCIOND message type: %s" % type_)
def parse_pathmgmt_payload(wrapper): # pragma: no cover type_ = wrapper.which() for cls_ in (PathSegmentReq, PathRecordsReply, PathRecordsReg, PathRecordsSync, RevocationInfo, IFStatePayload, IFStateRequest): if cls_.PAYLOAD_TYPE == type_: return cls_(getattr(wrapper, type_)) raise SCIONParseError("Unsupported path management type: %s" % type_)
def __init__(self, cert_list): """ :param list(Certificate) cert_list: certificate chain as list. """ if len(cert_list) != 2: raise SCIONParseError("Certificate chains must have length 2.") self.as_cert = cert_list[0] self.core_as_cert = cert_list[1]
def from_raw_multiple(cls, raw): assert isinstance(raw, bytes), type(raw) try: for p in cls.P_CLS.read_multiple_bytes_packed(raw): yield cls(p.as_builder()) except capnp.lib.capnp.KjException as e: raise SCIONParseError("Unable to parse %s capnp message: %s" % (cls, e)) from None
def parse_pcb_payload(type_, data): # pragma: no cover type_map = { PCBType.SEGMENT: PathSegment.from_raw, } if type_ not in type_map: raise SCIONParseError("Unsupported pcb type: %s", type_) handler = type_map[type_] return handler(data.pop())
def parse_ifid_payload(type_, data): type_map = { IFIDType.PAYLOAD: IFIDPayload.from_raw, } if type_ not in type_map: raise SCIONParseError("Unsupported IFID type: %s", type_) handler = type_map[type_] return handler(data.pop())
def parse_sibra_payload(type_, data): # pragma: no cover type_map = { SIBRAPayloadType.EMPTY: SIBRAPayload, } if type_ not in type_map: raise SCIONParseError("Unsupported sibra payload type: %s", type_) handler = type_map[type_] return handler.from_raw(data.pop())
def msg_from_raw(raw): try: wrapper = P.SCION.from_bytes_packed(raw).as_builder() except capnp.lib.capnp.KjException as e: raise SCIONParseError( "Unable to parse SCION capnp message: %s" % e) from None pld_class = wrapper.which() class_map = { PayloadClass.PCB: parse_pcb_payload, PayloadClass.IFID: parse_ifid_payload, PayloadClass.CERT: parse_certmgmt_payload, PayloadClass.PATH: parse_pathmgmt_payload, PayloadClass.SIBRA: parse_sibra_payload, } handler = class_map.get(pld_class) if not handler: raise SCIONParseError("Unsupported payload class: %s" % pld_class) return handler(getattr(wrapper, pld_class))
def parse_payload(self): data = Raw(self._payload.pack(), "SCIONL4Packet.parse_payload") if not self.l4_hdr: raise SCIONParseError("Cannot parse payload of non-L4 packet") if self.l4_hdr.TYPE == L4Proto.UDP: # Treat as SCION control message pld = self._parse_pld_ctrl(data) elif self.l4_hdr.TYPE == L4Proto.SCMP: pld = self._parse_pld_scmp(data) self.set_payload(pld) return pld
def parse_payload(self): if not self.l4_hdr: raise SCIONParseError("Cannot parse payload of non-L4 packet") praw = self._payload.pack() if self.l4_hdr.TYPE == L4Proto.UDP: # Treat as SCION control message pld = SignedCtrlPayload.from_raw(praw).pld() elif self.l4_hdr.TYPE == L4Proto.SCMP: pld = SCMPPayload((self.l4_hdr.class_, self.l4_hdr.type, praw)) self.set_payload(pld) return pld
def _parse(self, raw): """ Parse IPv6 address :param raw: Can be either `bytes` or `str` """ try: intf = IPv6Interface(raw) except AddressValueError as e: raise SCIONParseError("Unable to parse %s address: %s" % (self.name(), e)) from None self.addr = intf.ip
def from_proto(cls, p): # pragma: no cover """ Internal constructor, used by sub-classes to create the corresponding python object from a capnp object. The appropriate python class is selected by looking up the union field name in CLASS_FIELD_MAP. """ type_ = p.which() for cls_, field in cls.CLASS_FIELD_MAP.items(): if type_ == field: return cls._from_union(p, cls_.from_proto(getattr(p, type_))) raise SCIONParseError("Unsupported %s proto type: %s" % (cls.NAME, type_))
def parse_l4_hdr(proto, data, dst=None, src=None): if proto == L4Proto.UDP: raw_hdr = data.pop(SCIONUDPHeader.LEN) assert src assert dst return SCIONUDPHeader((src, dst, raw_hdr)) if proto == L4Proto.SCMP: raw_hdr = data.pop(SCMPHeader.LEN) return SCMPHeader((src, dst, raw_hdr)) if proto in L4Proto.L4: return None raise SCIONParseError("Unsupported L4 protocol type: %s" % proto)
def haddr_parse_interface(intf): """ Try to parse a string as either an ipv6 or ipv4 interface :param str interface: E.g. ``127.0.0.1/8``. """ for type_ in AddrType.IPV6, AddrType.IPV4: try: return haddr_parse(type_, intf) except SCIONParseError: pass else: raise SCIONParseError("Unable to parse interface '%s'" % intf)
def _parse_hex_as(self, raw, as_sep=HEX_SEPARATOR): try: as_parts = raw.split(as_sep) except ValueError: raise SCIONParseError("Unable to parse hex AS from string: %s" % raw) from None if len(as_parts) != self.HEX_AS_PARTS: raise SCIONParseError( "Wrong number of separators (%s) in hex AS number (expected: %d actual: %s): %s" % (self.HEX_SEPARATOR, self.HEX_AS_PARTS, as_parts, raw)) self._as = 0 for i, s in enumerate(as_parts): self._as <<= 16 v = int(s, base=16) if v > self.MAX_HEX_AS_PART: raise SCIONParseError( "Hex AS number has part greater than %x: %s" % (self.MAX_HEX_AS_PART, raw)) self._as |= v if self._as > self.MAX_AS: raise SCIONParseError("AS too large (max: %d): %s" % (self.MAX_AS, raw))
def _parse_pld_ctrl(self, data): pld_class = data.pop(1) class_map = { PayloadClass.PCB: parse_pcb_payload, PayloadClass.IFID: parse_ifid_payload, PayloadClass.CERT: parse_certmgmt_payload, PayloadClass.PATH: parse_pathmgmt_payload, PayloadClass.SIBRA: parse_sibra_payload, } handler = class_map.get(pld_class) if not handler: raise SCIONParseError("Unsupported payload class: %s" % pld_class) return handler(data.pop(1), data)