Ejemplo n.º 1
0
 def __init__(
     self,
     server_id,
     conf_dir,
 ):
     """
     :param str server_id: server identifier.
     :param str conf_dir: configuration directory.
     """
     super().__init__(
         server_id,
         conf_dir,
     )
     self.interface = None
     for border_router in self.topology.get_all_border_routers():
         if border_router.name == self.id:
             self.interface = border_router.interface
             break
     assert self.interface is not None
     logging.info("Interface: %s", self.interface.__dict__)
     self.is_core_router = self.topology.is_core_as
     self.of_gen_key = kdf(self.config.master_as_key, b"Derive OF Key")
     self.sibra_key = kdf(self.config.master_as_key, b"Derive SIBRA Key")
     self.if_states = defaultdict(InterfaceState)
     self.revocations = ExpiringDict(1000, self.FWD_REVOCATION_TIMEOUT)
     self.pre_ext_handlers = {
         SibraExtBase.EXT_TYPE: self.handle_sibra,
         TracerouteExt.EXT_TYPE: self.handle_traceroute,
         OneHopPathExt.EXT_TYPE: self.handle_one_hop_path,
         ExtHopByHopType.SCMP: self.handle_scmp,
     }
     self.post_ext_handlers = {
         SibraExtBase.EXT_TYPE: False,
         TracerouteExt.EXT_TYPE: False,
         ExtHopByHopType.SCMP: False,
         OneHopPathExt.EXT_TYPE: False,
     }
     self.sibra_state = SibraState(
         self.interface.bandwidth, "%s#%s -> %s" %
         (self.addr.isd_as, self.interface.if_id, self.interface.isd_as))
     self.CTRL_PLD_CLASS_MAP = {
         PayloadClass.IFID: {
             None: self.process_ifid_request
         },
         PayloadClass.PATH:
         defaultdict(lambda: self.process_path_mgmt_packet),
     }
     self.SCMP_PLD_CLASS_MAP = {
         SCMPClass.PATH: {
             SCMPPathClass.REVOKED_IF: self.process_revocation
         },
     }
     self._remote_sock = UDPSocket(
         bind=(str(self.interface.addr), self.interface.udp_port),
         addr_type=self.interface.addr.TYPE,
     )
     self._socks.add(self._remote_sock, self.handle_recv)
     logging.info("IP %s:%d", self.interface.addr, self.interface.udp_port)
Ejemplo n.º 2
0
 def test(self, _):
     inst = SibraState("bw", "isd as")
     inst.curr_tick = "curr tick"
     resvs = []
     for i in range(4):
         resv = create_mock(["next"])
         resv.next.return_value = bool(i % 2)
         resvs.append(resv)
     resv_dict = dict(enumerate(resvs))
     # Calls
     inst._resv_tick(resv_dict)
     # Tests
     for resv in resvs:
         resv.next.assert_called_once_with("curr tick")
     ntools.eq_(resv_dict, {1: resvs[1], 3: resvs[3]})
Ejemplo n.º 3
0
 def test_full(self, _):
     inst = SibraState("bw", "isd as")
     inst.curr_tick = "curr tick"
     resv = create_mock(["add"])
     bwhint_cls = create_mock(["floor"])
     bwhint = create_mock(["to_classes"])
     bwhint.to_classes.return_value = bwhint_cls
     resv.add.return_value = bwhint
     # Call
     ntools.eq_(inst._add(resv, "resv idx", "bwsnap", "exp_tick", False),
                bwhint_cls.floor.return_value)
     # Tests
     resv.add.assert_called_once_with("resv idx", "bwsnap", "exp_tick",
                                      "curr tick")
     bwhint.to_classes.assert_called_once_with(floor=True)
Ejemplo n.º 4
0
 def test(self, _, curr_tick):
     inst = SibraState("bw", "isd as")
     inst.curr_tick = 0
     inst.link = create_mock(["next"])
     inst.steady = "steady"
     inst.ephemeral = "ephemeral"
     inst._resv_tick = create_mock()
     curr_tick.return_value = 3
     # Call
     inst.update_tick()
     # Tests
     ntools.eq_(inst.curr_tick, 3)
     assert_these_calls(inst.link.next, [call()] * 3)
     assert_these_calls(inst._resv_tick,
                        [call("steady"), call("ephemeral")] * 3)
Ejemplo n.º 5
0
 def test_setup_success(self, _):
     inst = SibraState("bw", "isd as")
     inst.update_tick = create_mock()
     inst._create_ephemeral = create_mock()
     inst._create_ephemeral.return_value = "resv"
     inst._add = create_mock()
     inst._add.return_value = None
     inst.steady["steady id"] = create_mock(["add_child"])
     # Call
     inst.add_ephemeral("path id", "steady id", "resv idx", "bwsnap",
                        "exp_tick", "accepted")
     # Tests
     inst.update_tick.assert_called_once_with()
     inst._create_ephemeral.assert_called_once_with("path id", "steady id")
     inst._add.assert_called_once_with(
         "resv", "resv idx", "bwsnap", "exp_tick", "accepted")
     ntools.eq_(inst.ephemeral, {"path id": "resv"})
     ntools.eq_(inst.pend_ephemeral, {"path id": True})
     inst.steady["steady id"].add_child.assert_called_once_with("path id")
Ejemplo n.º 6
0
 def test_setup_success(self, _):
     inst = SibraState("bw", "isd as")
     inst.update_tick = create_mock()
     inst._create_steady = create_mock()
     inst._create_steady.return_value = "resv"
     inst._add = create_mock()
     inst._add.return_value = None
     # Call
     inst.add_steady("path id", "resv idx", "bwsnap", "exp_tick", "accepted")
     # Tests
     inst.update_tick.assert_called_once_with()
     inst._create_steady.assert_called_once_with("path id")
     inst._add.assert_called_once_with(
         "resv", "resv idx", "bwsnap", "exp_tick", "accepted")
     ntools.eq_(inst.steady, {"path id": "resv"})
     ntools.eq_(inst.pend_steady, {"path id": True})
Ejemplo n.º 7
0
 def test_renewal_denied(self, _):
     inst = SibraState("bw", "isd as")
     inst.update_tick = create_mock()
     inst._add = create_mock()
     inst.steady["path id"] = "resv"
     # Call
     ntools.eq_(
         inst.add_steady("path id", "resv idx", "bwsnap", "exp_tick",
                         "accepted", setup=False),
         inst._add.return_value)
     # Tests
     inst._add.assert_called_once_with(
         "resv", "resv idx", "bwsnap", "exp_tick", "accepted")
Ejemplo n.º 8
0
 def _find_links(self):
     for br in self.topology.border_routers:
         for ifid, intf in br.interfaces.items():
             self.link_states[ifid] = SibraState(intf.bandwidth,
                                                 self.addr.isd_as)
             self.link_types[ifid] = intf.link_type
Ejemplo n.º 9
0
class Router(SCIONElement):
    """
    The SCION Router.

    :ivar interface: the router's inter-AS interface, if any.
    :type interface: :class:`lib.topology.InterfaceElement`
    """
    SERVICE_TYPE = ROUTER_SERVICE
    FWD_REVOCATION_TIMEOUT = 5
    IFSTATE_REQ_INTERVAL = 30

    def __init__(self, server_id, conf_dir, ):
        """
        :param str server_id: server identifier.
        :param str conf_dir: configuration directory.
        """
        super().__init__(server_id, conf_dir, )
        self.interface = None
        for border_router in self.topology.get_all_border_routers():
            if border_router.name == self.id:
                self.interface = border_router.interface
                break
        assert self.interface is not None
        logging.info("Interface: %s", self.interface.__dict__)
        self.is_core_router = self.topology.is_core_as
        self.of_gen_key = kdf(self.config.master_as_key, b"Derive OF Key")
        self.sibra_key = kdf(self.config.master_as_key, b"Derive SIBRA Key")
        self.if_states = defaultdict(InterfaceState)
        self.revocations = ExpiringDict(1000, self.FWD_REVOCATION_TIMEOUT)
        self.pre_ext_handlers = {
            SibraExtBase.EXT_TYPE: self.handle_sibra,
            TracerouteExt.EXT_TYPE: self.handle_traceroute,
            OneHopPathExt.EXT_TYPE: self.handle_one_hop_path,
            ExtHopByHopType.SCMP: self.handle_scmp,
        }
        self.post_ext_handlers = {
            SibraExtBase.EXT_TYPE: False, TracerouteExt.EXT_TYPE: False,
            ExtHopByHopType.SCMP: False, OneHopPathExt.EXT_TYPE: False,
        }
        self.sibra_state = SibraState(
            self.interface.bandwidth,
            "%s#%s -> %s" % (self.addr.isd_as, self.interface.if_id,
                             self.interface.isd_as))
        self.CTRL_PLD_CLASS_MAP = {
            PayloadClass.IFID: {None: self.process_ifid_request},
            PayloadClass.PATH: defaultdict(
                lambda: self.process_path_mgmt_packet),
        }
        self.SCMP_PLD_CLASS_MAP = {
            SCMPClass.PATH: {SCMPPathClass.REVOKED_IF: self.process_revocation},
        }
        self._remote_sock = UDPSocket(
            bind=(str(self.interface.addr), self.interface.udp_port),
            addr_type=self.interface.addr.TYPE,
        )
        self._socks.add(self._remote_sock, self.handle_recv)
        logging.info("IP %s:%d", self.interface.addr, self.interface.udp_port)

    def _setup_sockets(self, init=True):
        """
        Setup incoming socket
        """
        self._udp_sock = UDPSocket(
            bind=(str(self.addr.host), self._port, self.id),
            addr_type=self.addr.host.TYPE,
        )
        self._port = self._udp_sock.port
        self._socks.add(self._udp_sock, self.handle_recv)

    def run(self):
        """
        Run the router threads.
        """
        threading.Thread(
            target=thread_safety_net, args=(self.sync_interface,),
            name="BR.sync_interface", daemon=True).start()
        threading.Thread(
            target=thread_safety_net, args=(self.request_ifstates,),
            name="BR.request_ifstates", daemon=True).start()
        threading.Thread(
            target=thread_safety_net, args=(self.sibra_worker,),
            name="BR.sibra_worker", daemon=True).start()
        SCIONElement.run(self)

    def send(self, spkt, addr, port):
        """
        Send a spkt to addr (class of that object must implement
        __str__ which returns IP addr string) using port and local or remote
        socket.

        :param spkt: The packet to send.
        :type spkt: :class:`lib.spkt.SCIONspkt`
        :param addr: The address of the next hop.
        :type addr: :class:`HostAddrBase`
        :param int port: The port number of the next hop.
        """
        from_local_as = addr == self.interface.to_addr
        self.handle_extensions(spkt, False, from_local_as)
        if from_local_as:
            self._remote_sock.send(spkt.pack(), (str(addr), port))
        else:
            self._udp_sock.send(spkt.pack(), (str(addr), port))

    def handle_extensions(self, spkt, pre_routing_phase, from_local_as):
        """
        Handle SCION Packet extensions. Handlers can be defined for pre- and
        post-routing.
        """
        if pre_routing_phase:
            prefix = "pre"
            handlers = self.pre_ext_handlers
        else:
            prefix = "post"
            handlers = self.post_ext_handlers
        flags = []
        # Hop-by-hop extensions must be first (just after path), and process
        # only MAX_HOPBYHOP_EXT number of them. If an SCMP ext header is
        # present, it must be the first hopbyhop extension (and isn't included
        # in the MAX_HOPBYHOP_EXT check).
        count = 0
        for i, ext_hdr in enumerate(spkt.ext_hdrs):
            if ext_hdr.EXT_CLASS != ExtensionClass.HOP_BY_HOP:
                break
            if ext_hdr.EXT_TYPE == ExtHopByHopType.SCMP:
                if i != 0:
                    logging.error("SCMP ext header not first.")
                    raise SCMPBadExtOrder(i)
            else:
                count += 1
            if count > MAX_HOPBYHOP_EXT:
                logging.error("Too many hop-by-hop extensions.")
                raise SCMPTooManyHopByHop(i)
            handler = handlers.get(ext_hdr.EXT_TYPE)
            if handler is None:
                logging.debug("No %s-handler for extension type %s",
                              prefix, ext_hdr.EXT_TYPE)
                raise SCMPBadHopByHop
            if handler:
                flags.extend(handler(ext_hdr, spkt, from_local_as))
        return flags

    def handle_traceroute(self, hdr, spkt, _):
        hdr.append_hop(self.addr.isd_as, self.interface.if_id)
        return []

    def handle_one_hop_path(self, hdr, spkt, from_local_as):
        if len(spkt.path) != InfoOpaqueField.LEN + 2 * HopOpaqueField.LEN:
            logging.error("OneHopPathExt: incorrect path length.")
            return [(RouterFlag.ERROR,)]
        if not from_local_as:  # Remote packet, create the 2nd Hop Field
            info = spkt.path.get_iof()
            hf1 = spkt.path.get_hof_ver(ingress=True)
            exp_time = OneHopPathExt.HOF_EXP_TIME
            hf2 = HopOpaqueField.from_values(exp_time, self.interface.if_id, 0)
            hf2.set_mac(self.of_gen_key, info.timestamp, hf1)
            # FIXME(PSz): quite brutal for now:
            spkt.path = SCIONPath.from_values(info, [hf1, hf2])
            spkt.path.inc_hof_idx()
        return []

    def handle_sibra(self, hdr, spkt, from_local_as):
        ret = hdr.process(self.sibra_state, spkt, from_local_as,
                          self.sibra_key)
        logging.debug("Sibra state:\n%s", self.sibra_state)
        return ret

    def handle_scmp(self, hdr, spkt, _):
        if hdr.hopbyhop:
            return [(RouterFlag.PROCESS_LOCAL,)]
        return []

    def sync_interface(self):
        """
        Synchronize and initialize the router's interface with that of a
        neighboring router.
        """
        ifid_pld = IFIDPayload.from_values(self.interface.if_id)
        pkt = self._build_packet(self.interface.to_addr,
                                 dst_ia=self.interface.isd_as)
        while self.run_flag.is_set():
            pkt.set_payload(ifid_pld.copy())
            self.send(pkt, self.interface.to_addr, self.interface.to_udp_port)
            time.sleep(IFID_PKT_TOUT)

    def request_ifstates(self):
        """
        Periodically request interface states from the BS.
        """
        pld = IFStateRequest.from_values()
        while self.run_flag.is_set():
            start_time = SCIONTime.get_time()
            logging.info("Sending IFStateRequest for all interfaces.")
            for bs in self.topology.beacon_servers:
                req = self._build_packet(bs.addr, dst_port=bs.port,
                                         payload=pld.copy())
                self.send(req, bs.addr, SCION_UDP_EH_DATA_PORT)
            sleep_interval(start_time, self.IFSTATE_REQ_INTERVAL,
                           "request_ifstates")

    def sibra_worker(self):
        while self.run_flag.is_set():
            start_time = SCIONTime.get_time()
            self.sibra_state.update_tick()
            sleep_interval(start_time, 1.0, "sibra_worker")

    def process_ifid_request(self, pkt, from_local):
        """
        After receiving IFID_PKT from neighboring router it is completed (by
        iface information) and passed to local BSes.

        :param ifid_packet: the IFID request packet to send.
        :type ifid_packet: :class:`lib.packet.scion.IFIDPacket`
        """
        if from_local:
            logging.error("Received IFID packet from local AS, dropping")
            return
        ifid_pld = pkt.get_payload().copy()
        # Forward 'alive' packet to all BSes (to inform that neighbor is alive).
        # BS must determine interface.
        ifid_pld.p.relayIF = self.interface.if_id
        try:
            bs_addrs = self.dns_query_topo(BEACON_SERVICE)
        except SCIONServiceLookupError as e:
            logging.error("Unable to deliver ifid packet: %s", e)
            raise SCMPUnknownHost
        # Only deliver once per address, as the multicast SVC address will cover
        # all instances on that address.
        pkt = self._build_packet(SVCType.BS_M)
        for addr in set([a for a, _ in bs_addrs]):
            pkt.set_payload(ifid_pld.copy())
            self.send(pkt, addr, SCION_UDP_EH_DATA_PORT)

    def get_srv_addr(self, service, pkt):
        """
        For a given service return a server address. Guarantee that all packets
        from the same source to a given service are sent to the same server.

        :param str service: Service to query for.
        :type pkt: :class:`lib.packet.scion.SCIONBasePacket`

        """
        addrs = self.dns_query_topo(service)
        addrs.sort()  # To not rely on order of DNS replies.
        return addrs[zlib.crc32(pkt.addrs.pack()) % len(addrs)][0]

    def process_path_mgmt_packet(self, mgmt_pkt, from_local_as):
        """
        Process path management packets.

        :param mgmt_pkt: The path mgmt packet.
        :type mgmt_pkt: :class:`lib.packet.path_mgmt.PathMgmtPacket`
        :param bool from_local_as:
            whether or not the packet is from the local AS.
        """
        payload = mgmt_pkt.get_payload()
        if payload.PAYLOAD_TYPE == PMT.IFSTATE_INFOS:
            # handle state update
            logging.debug("Received IFState update:\n%s",
                          str(mgmt_pkt.get_payload()))
            for p in payload.p.infos:
                self.if_states[p.ifID].update(IFStateInfo(p))
            return
        self.handle_data(mgmt_pkt, from_local_as)

    def process_revocation(self, spkt, from_local_as):
        pld = spkt.get_payload()
        logging.info("Processing revocation: %s", pld.info)
        # First, forward the packet as appropriate.
        self.handle_data(spkt, from_local_as)
        if from_local_as:
            return
        # Forward to local path and beacon services if we haven't recently.
        rev_info = RevocationInfo.from_raw(pld.info.rev_info)
        if rev_info in self.revocations:
            return
        snames = []
        # Fork revocation to local BS and PS if router is downstream of the
        # failed interface.
        if (spkt.addrs.src.isd_as[0] == self.addr.isd_as[0] and
                self._is_downstream_router()):
            snames.append(BEACON_SERVICE)
            if self.topology.path_servers:
                snames.append(PATH_SERVICE)
        # Fork revocation to local PS if router is in the AS of the source.
        elif (spkt.addrs.dst.isd_as == self.addr.isd_as and
                self.topology.path_servers):
            snames.append(PATH_SERVICE)

        self.revocations[rev_info] = True
        for sname in snames:
            try:
                addr, port = self.dns_query_topo(sname)[0]
            except SCIONServiceLookupError:
                logging.error("Unable to find %s to forward revocation to.",
                              sname)
                continue
            pkt = self._build_packet(addr, dst_port=port,
                                     payload=rev_info.copy())
            self.send(pkt, addr, SCION_UDP_EH_DATA_PORT)

    def _is_downstream_router(self):
        """
        Returns True if this router is connected to an upstream router (via an
        upstream link), False otherwise.
        """
        return self.interface.link_type == LinkType.PARENT

    def send_revocation(self, spkt, if_id, ingress, path_incd):
        """
        Sends an interface revocation for 'if_id' along the path in 'spkt'.
        """
        logging.info("Interface %d is down. Issuing revocation.", if_id)
        # Check that the interface is really down.
        if_state = self.if_states[if_id]
        if self.if_states[if_id].is_active:
            logging.error("Interface %d appears to be up. Not sending " +
                          "revocation." % if_id)
            return

        assert if_state.rev_info, "Revocation token missing."

        rev_pkt = spkt.reversed_copy()
        rev_pkt.convert_to_scmp_error(
            self.addr, SCMPClass.PATH, SCMPPathClass.REVOKED_IF, spkt, if_id,
            ingress, if_state.rev_info.copy(), hopbyhop=True)
        if path_incd:
            rev_pkt.path.inc_hof_idx()
        rev_pkt.update()
        logging.debug("Revocation Packet:\n%s" % rev_pkt.short_desc())
        # FIXME(kormat): In some circumstances, this doesn't actually work, as
        # handle_data will try to send the packet to this interface first, and
        # then drop the packet as the interface is down.
        self.handle_data(rev_pkt, ingress, drop_on_error=True)

    def deliver(self, spkt, force=True):
        """
        Forwards the packet to the end destination within the current AS.

        :param spkt: The SCION Packet to forward.
        :type spkt: :class:`lib.packet.scion.SCIONPacket`
        :param bool force:
            If set, allow packets to be delivered locally that would otherwise
            be disallowed.
        """
        if not force and spkt.addrs.dst.isd_as != self.addr.isd_as:
            logging.error("Tried to deliver a non-local packet:\n%s", spkt)
            raise SCMPDeliveryNonLocal
        if len(spkt.path):
            hof = spkt.path.get_hof()
            if not force and hof.forward_only:
                raise SCMPDeliveryFwdOnly
            if hof.verify_only:
                raise SCMPNonRoutingHOF
        # Forward packet to destination.
        addr = spkt.addrs.dst.host
        if addr.TYPE == AddrType.SVC:
            # Send request to any server.
            try:
                service = SVC_TO_SERVICE[addr.addr]
                addr = self.get_srv_addr(service, spkt)
            except SCIONServiceLookupError as e:
                logging.error("Unable to deliver path mgmt packet: %s", e)
                raise SCMPUnknownHost
        self.send(spkt, addr, SCION_UDP_EH_DATA_PORT)

    def verify_hof(self, path, ingress=True):
        """Verify freshness and authentication of an opaque field."""
        iof = path.get_iof()
        ts = iof.timestamp
        hof = path.get_hof()
        prev_hof = path.get_hof_ver(ingress=ingress)
        # Check that the interface in the current hop field matches the
        # interface in the router.

        if path.get_curr_if(ingress=ingress) != self.interface.if_id:
            raise SCIONIFVerificationError(hof, iof)

        if int(SCIONTime.get_time()) <= ts + hof.exp_time * EXP_TIME_UNIT:
            if not hof.verify_mac(self.of_gen_key, ts, prev_hof):
                raise SCIONOFVerificationError(hof, prev_hof)
        else:
            raise SCIONOFExpiredError(hof)

    def _egress_forward(self, spkt):
        logging.debug("Forwarding to remote interface: %s:%s",
                      self.interface.to_addr, self.interface.to_udp_port)
        self.send(spkt, self.interface.to_addr, self.interface.to_udp_port)

    def handle_data(self, spkt, from_local_as, drop_on_error=False):
        """
        Main entry point for data packet handling.

        :param spkt: The SCION Packet to process.
        :type spkt: :class:`lib.packet.scion.SCIONPacket`
        :param from_local_as:
            Whether or not the packet is from the local AS.
        """
        if len(spkt.path) == 0:
            raise SCMPPathRequired
        ingress = not from_local_as
        try:
            self._process_data(spkt, ingress, drop_on_error)
        except SCIONIFVerificationError as e:
            logging.error("Dropping packet due to not matching interfaces.\n"
                          "Current IOF: %s\nCurrent HOF: %s\n"
                          "Router Interface: %d" %
                          (e.args[1], e.args[0], self.interface.if_id))
        except SCIONOFVerificationError as e:
            logging.error("Dropping packet due to incorrect MAC.\n"
                          "Header:\n%s\nInvalid OF: %s\nPrev OF: %s",
                          spkt, e.args[0], e.args[1])
            raise SCMPBadMAC from None
        except SCIONOFExpiredError as e:
            logging.error("Dropping packet due to expired OF.\n"
                          "Header:\n%s\nExpired OF: %s",
                          spkt, e)
            raise SCMPExpiredHOF from None
        except SCIONPacketHeaderCorruptedError:
            logging.error("Dropping packet due to invalid header state.\n"
                          "Header:\n%s", spkt)
        except SCIONSegmentSwitchError as e:
            logging.error("Dropping packet due to disallowed segment switch: "
                          "%s" % e.args[0])
        except SCIONInterfaceDownException:
            logging.info("Dropping packet due to interface being down.")
            pass

    def _process_data(self, spkt, ingress, drop_on_error):
        path = spkt.path
        if len(spkt) > self.topology.mtu:
            # FIXME(kormat): ignore this check for now, as PCB packets are often
            # over MTU, it's just that udp-overlay handles fragmentation for us.
            # Once we have TCP/SCION, this check should be re-instated.
            # This also needs to look at the specific MTU for the relevant link
            # if on egress.
            #  raise SCMPOversizePkt("Packet larger than mtu", mtu)
            pass
        self.verify_hof(path, ingress=ingress)
        hof = spkt.path.get_hof()
        if hof.verify_only:
            raise SCMPNonRoutingHOF
        # FIXME(aznair): Remove second condition once PathCombinator is less
        # stupid.
        if (spkt.addrs.dst.isd_as == self.addr.isd_as and
                spkt.path.is_on_last_segment()):
            self.deliver(spkt)
            return
        if ingress:
            prev_if = path.get_curr_if()
            prev_iof = path.get_iof()
            prev_hof = path.get_hof()
            prev_iof_idx = path.get_of_idxs()[0]
            fwd_if, path_incd, skipped_vo = self._calc_fwding_ingress(spkt)
            cur_iof_idx = path.get_of_idxs()[0]
            if prev_iof_idx != cur_iof_idx:
                self._validate_segment_switch(
                    path, fwd_if, prev_if, prev_iof, prev_hof)
            elif skipped_vo:
                raise SCIONSegmentSwitchError("Skipped verify only field, but "
                                              "did not switch segments.")
        else:
            fwd_if = path.get_fwd_if()
            path_incd = False
        try:
            br = self.ifid2br[fwd_if]
            if_addr, port = br.addr, br.port
        except KeyError:
            # So that the error message will show the current state of the
            # packet.
            spkt.update()
            logging.error("Cannot forward packet, fwd_if is invalid (%s):\n%s",
                          fwd_if, spkt)
            raise SCMPBadIF(fwd_if) from None
        if not self.if_states[fwd_if].is_active:
            if drop_on_error:
                logging.debug("IF is down, but drop_on_error is set, dropping")
                return
            self.send_revocation(spkt, fwd_if, ingress, path_incd)
            return
        if ingress:
            logging.debug("Sending to IF %s (%s:%s)", fwd_if, if_addr, port)
            self.send(spkt, if_addr, port)
        else:
            path.inc_hof_idx()
            self._egress_forward(spkt)

    def _validate_segment_switch(self, path, fwd_if, prev_if, prev_iof,
                                 prev_hof):
        """
        Validates switching of segments according to the following rules:

        1) Never switch from a down-segment to an up-segment
           (valley-freeness)
        2) Never switch from an up(down)-segment to an up(down)-segment, if the
           packet is not forwarded(received) over a CORE link.
        3) Never switch from a core-segment to a core-segment.
        4) If a packet is received over a peering link, check on ingress that
           the egress IF is the same for both the current and next hop fields.
        5) If a packet is to be forwarded over a peering link, check on ingress
           that the ingress IF is the same for both current and next hop fields.
        """
        rcvd_on_link_type = self._link_type(prev_if)
        fwd_on_link_type = self._link_type(fwd_if)
        cur_iof = path.get_iof()
        cur_hof = path.get_hof()
        if not prev_iof.up_flag and cur_iof.up_flag:
            raise SCIONSegmentSwitchError(
                "Switching from down- to up-segment is not allowed.")
        if (prev_iof.up_flag and cur_iof.up_flag and
                fwd_on_link_type != LinkType.CORE):
            raise SCIONSegmentSwitchError(
                "Switching from up- to up-segment is not allowed "
                "if the packet is not forwarded over a CORE link.")
        if (not prev_iof.up_flag and not cur_iof.up_flag and
                rcvd_on_link_type != LinkType.CORE):
            raise SCIONSegmentSwitchError(
                "Switching from down- to down-segment is not "
                "allowed if the packet was not received over a CORE link.")
        if (rcvd_on_link_type == LinkType.CORE and
                fwd_on_link_type == LinkType.CORE):
            raise SCIONSegmentSwitchError(
                "Switching from core- to core-segment is not allowed.")
        if ((rcvd_on_link_type == LinkType.PEER or
                fwd_on_link_type == LinkType.PEER) and
                prev_hof.egress_if != cur_hof.egress_if):
            raise SCIONSegmentSwitchError(
                "Egress IF of peering HOF does not match egress IF of current "
                "HOF.")

    def _calc_fwding_ingress(self, spkt):
        path = spkt.path
        hof = path.get_hof()
        incd = False
        skipped_vo = False
        if hof.xover:
            skipped_vo = path.inc_hof_idx()
            incd = True
        return path.get_fwd_if(), incd, skipped_vo

    def _link_type(self, if_id):
        """
        Returns the link type of the link corresponding to 'if_id' or None.
        """
        for br in self.topology.get_all_border_routers():
            if br.interface.if_id == if_id:
                return br.interface.link_type
        return None

    def _needs_local_processing(self, pkt):
        return pkt.addrs.dst in [
            self.addr,
            SCIONAddr.from_values(self.addr.isd_as, self.interface.addr),
        ]

    def _process_flags(self, flags, pkt, from_local_as):
        """
        Go through the flags set by hop-by-hop extensions on this packet.
        :returns:
        """
        process = False
        # First check if any error or no_process flags are set
        for (flag, *args) in flags:
            if flag == RouterFlag.ERROR:
                logging.error("%s", args[0])
                return True, False
            elif flag == RouterFlag.NO_PROCESS:
                return True, False
        # Now check for other flags
        for (flag, *args) in flags:
            if flag == RouterFlag.FORWARD:
                if from_local_as:
                    self._process_fwd_flag(pkt)
                else:
                    self._process_fwd_flag(pkt, args[0])
                return True, False
            elif flag in (RouterFlag.DELIVER, RouterFlag.FORCE_DELIVER):
                self._process_deliver_flag(pkt, flag)
                return True, False
            elif flag == RouterFlag.PROCESS_LOCAL:
                process = True
        return False, process

    def _process_fwd_flag(self, pkt, ifid=None):
        if ifid is None:
            logging.debug("Packet forwarded over link by extension")
            self._egress_forward(pkt)
            return
        if ifid == 0:
            logging.error("Extension asked to forward this to interface 0:\n%s",
                          pkt)
            return
        next_hop = self.ifid2br[ifid]
        logging.debug("Packet forwarded by extension via %s:%s",
                      next_hop.addr, next_hop.port)
        self.send(pkt, next_hop.addr, next_hop.port)

    def _process_deliver_flag(self, pkt, flag):
        if (flag == RouterFlag.DELIVER and
                pkt.addrs.dst.isd_as != self.addr.isd_as):
            logging.error("Extension tried to deliver this locally, but this "
                          "is not the destination ISD-AS:\n%s", pkt)
            return
        logging.debug("Packet delivered by extension")
        self.deliver(pkt)

    def _get_msg_meta(self, packet, addr, sock):
        meta = RawMetadata.from_values(packet, addr, sock == self._udp_sock)
        return packet, meta

    def handle_msg_meta(self, msg, meta):
        """
        Main routine to handle incoming SCION messages.
        """
        self.handle_request(meta.packet, meta.addr, meta.from_local_as)

    def handle_request(self, packet, _, from_local_socket=True, sock=None):
        """
        Main routine to handle incoming SCION packets.

        :param bytes packet: The incoming packet to handle.
        :param tuple sender: Tuple of sender IP, port.
        :param bool from_local_socket:
            True, if the packet was received on the local socket.
        """
        from_local_as = from_local_socket
        pkt = self._parse_packet(packet)
        if not pkt:
            return
        try:
            flags = self.handle_extensions(pkt, True, from_local_as)
        except SCMPError as e:
            self._scmp_validate_error(pkt, e)
            return
        stop, needs_local = self._process_flags(flags, pkt, from_local_as)
        if stop:
            logging.debug("Stopped processing")
            return
        try:
            needs_local |= self._needs_local_processing(pkt)
        except SCMPError as e:
            self._scmp_validate_error(pkt, e)
            return
        if needs_local:
            try:
                pkt.parse_payload()
            except SCIONBaseError:
                log_exception("Error parsing payload:\n%s" % hex_str(packet))
                return
            handler = self._get_handler(pkt)
        else:
            # It's a normal packet, just forward it.
            handler = self.handle_data
        logging.debug("handle_request (from_local_as? %s):"
                      "\n  %s\n  %s\n  handler: %s",
                      from_local_as, pkt.cmn_hdr, pkt.addrs, handler)
        if not handler:
            return
        try:
            handler(pkt, from_local_as)
        except SCMPError as e:
            self._scmp_validate_error(pkt, e)
        except SCIONBaseError:
            log_exception("Error handling packet: %s" % pkt)
Ejemplo n.º 10
0
 def _find_links(self):
     for er in self.topology.get_all_edge_routers():
         iface = er.interface
         self.link_states[iface.if_id] = SibraState(iface.bandwidth,
                                                    self.addr.isd_as)
         self.link_types[iface.if_id] = iface.link_type
Ejemplo n.º 11
0
class Router(SCIONElement):
    """
    The SCION Router.

    :ivar interface: the router's inter-AS interface, if any.
    :type interface: :class:`lib.topology.InterfaceElement`
    """
    SERVICE_TYPE = ROUTER_SERVICE
    FWD_REVOCATION_TIMEOUT = 5
    IFSTATE_REQ_INTERVAL = 30

    def __init__(
        self,
        server_id,
        conf_dir,
    ):
        """
        :param str server_id: server identifier.
        :param str conf_dir: configuration directory.
        """
        super().__init__(
            server_id,
            conf_dir,
        )
        self.interface = None
        for edge_router in self.topology.get_all_edge_routers():
            if edge_router.addr == self.addr.host:
                self.interface = edge_router.interface
                break
        assert self.interface is not None
        logging.info("Interface: %s", self.interface.__dict__)
        self.of_gen_key = PBKDF2(self.config.master_as_key, b"Derive OF Key")
        self.sibra_key = PBKDF2(self.config.master_as_key, b"Derive SIBRA Key")
        self.if_states = defaultdict(InterfaceState)
        self.revocations = ExpiringDict(1000, self.FWD_REVOCATION_TIMEOUT)
        self.pre_ext_handlers = {
            SibraExtBase.EXT_TYPE:
            self.handle_sibra,
            TracerouteExt.EXT_TYPE:
            self.handle_traceroute,
            ExtHopByHopType.SCMP:
            self.handle_scmp,
            HORNETPlugin.EXT_TYPE:
            HORNETPlugin(self.id, conf_dir, self.config.master_as_key,
                         self.addr, self.interface).pre_routing
        }
        self.post_ext_handlers = {
            SibraExtBase.EXT_TYPE: False,
            TracerouteExt.EXT_TYPE: False,
            ExtHopByHopType.SCMP: False,
            HORNETPlugin.EXT_TYPE: False
        }
        self.sibra_state = SibraState(
            self.interface.bandwidth, "%s#%s -> %s" %
            (self.addr.isd_as, self.interface.if_id, self.interface.isd_as))
        self.CTRL_PLD_CLASS_MAP = {
            PayloadClass.PCB: {
                PCBType.SEGMENT: self.process_pcb
            },
            PayloadClass.IFID: {
                IFIDType.PAYLOAD: self.process_ifid_request
            },
            PayloadClass.CERT:
            defaultdict(lambda: self.relay_cert_server_packet),
            PayloadClass.PATH:
            defaultdict(lambda: self.process_path_mgmt_packet),
            PayloadClass.SIBRA: {
                SIBRAPayloadType.EMPTY: self.fwd_sibra_service_pkt
            },
        }
        self.SCMP_PLD_CLASS_MAP = {
            SCMPClass.PATH: {
                SCMPPathClass.REVOKED_IF: self.process_revocation
            },
        }
        self._remote_sock = UDPSocket(
            bind=(str(self.interface.addr), self.interface.udp_port),
            addr_type=AddrType.IPV4,
        )
        self._socks.add(self._remote_sock, self.handle_recv)
        logging.info("IP %s:%d", self.interface.addr, self.interface.udp_port)

    def _setup_socket(self, init=True):
        """
        Setup incoming socket
        """
        # FIXME(kormat): reuse=True should to away once the dispatcher and the
        # router no longer try binding to the same socket.
        self._local_sock = UDPSocket(
            bind=(str(self.addr.host), SCION_UDP_EH_DATA_PORT, self.id),
            addr_type=self.addr.host.TYPE,
            reuse=True,
        )
        self._port = self._local_sock.port
        self._socks.add(self._local_sock, self.handle_recv)

    def run(self):
        """
        Run the router threads.
        """
        threading.Thread(target=thread_safety_net,
                         args=(self.sync_interface, ),
                         name="ER.sync_interface",
                         daemon=True).start()
        threading.Thread(target=thread_safety_net,
                         args=(self.request_ifstates, ),
                         name="ER.request_ifstates",
                         daemon=True).start()
        threading.Thread(target=thread_safety_net,
                         args=(self.sibra_worker, ),
                         name="ER.sibra_worker",
                         daemon=True).start()
        SCIONElement.run(self)

    def send(self, spkt, addr=None, port=SCION_UDP_EH_DATA_PORT):
        """
        Send a spkt to addr (class of that object must implement
        __str__ which returns IPv4 addr) using port and local or remote
        socket. If addr isn't set, use the destination address in the packet.

        :param spkt: The packet to send.
        :type spkt: :class:`lib.spkt.SCIONspkt`
        :param addr: The address of the next hop.
        :type addr: :class:`IPv4Adress`
        :param int port: The port number of the next hop.
        """
        if addr is None:
            addr = spkt.addrs.dst.host
        from_local_as = addr == self.interface.to_addr
        self.handle_extensions(spkt, False, from_local_as)
        if from_local_as:
            self._remote_sock.send(spkt.pack(), (str(addr), port))
        else:
            self._local_sock.send(spkt.pack(), (str(addr), port))

    def handle_extensions(self, spkt, pre_routing_phase, from_local_as):
        """
        Handle SCION Packet extensions. Handlers can be defined for pre- and
        post-routing.
        """
        if pre_routing_phase:
            prefix = "pre"
            handlers = self.pre_ext_handlers
        else:
            prefix = "post"
            handlers = self.post_ext_handlers
        flags = []
        # Hop-by-hop extensions must be first (just after path), and process
        # only MAX_HOPBYHOP_EXT number of them. If an SCMP ext header is
        # present, it must be the first hopbyhop extension (and isn't included
        # in the MAX_HOPBYHOP_EXT check).
        count = 0
        for i, ext_hdr in enumerate(spkt.ext_hdrs):
            if ext_hdr.EXT_CLASS != ExtensionClass.HOP_BY_HOP:
                break
            if ext_hdr.EXT_TYPE == ExtHopByHopType.SCMP:
                if i != 0:
                    logging.error("SCMP ext header not first.")
                    raise SCMPBadExtOrder(i)
            else:
                count += 1
            if count > MAX_HOPBYHOP_EXT:
                logging.error("Too many hop-by-hop extensions.")
                raise SCMPTooManyHopByHop(i)
            handler = handlers.get(ext_hdr.EXT_TYPE)
            if handler is None:
                logging.debug("No %s-handler for extension type %s", prefix,
                              ext_hdr.EXT_TYPE)
                raise SCMPBadHopByHop
            if handler:
                flags.extend(handler(ext_hdr, spkt, from_local_as))
        return flags

    def handle_traceroute(self, hdr, spkt, _):
        # Truncate milliseconds to 2B
        hdr.append_hop(self.addr.isd_as, self.interface.if_id)
        return []

    def handle_sibra(self, hdr, spkt, from_local_as):
        ret = hdr.process(self.sibra_state, spkt, from_local_as,
                          self.sibra_key)
        logging.debug("Sibra state:\n%s", self.sibra_state)
        return ret

    def handle_scmp(self, hdr, spkt, _):
        if hdr.hopbyhop:
            return [(RouterFlag.PROCESS_LOCAL, )]
        return []

    def sync_interface(self):
        """
        Synchronize and initialize the router's interface with that of a
        neighboring router.
        """
        ifid_pld = IFIDPayload.from_values(self.interface.if_id)
        pkt = self._build_packet(SVCType.BS, dst_ia=self.interface.isd_as)
        while self.run_flag.is_set():
            pkt.set_payload(ifid_pld.copy())
            self.send(pkt, self.interface.to_addr, self.interface.to_udp_port)
            time.sleep(IFID_PKT_TOUT)

    def request_ifstates(self):
        """
        Periodically request interface states from the BS.
        """
        pld = IFStateRequest.from_values()
        req = self._build_packet()
        while self.run_flag.is_set():
            start_time = SCIONTime.get_time()
            logging.info("Sending IFStateRequest for all interfaces.")
            for bs in self.topology.beacon_servers:
                req.addrs.dst.host = bs.addr
                req.set_payload(pld.copy())
                self.send(req)
            sleep_interval(start_time, self.IFSTATE_REQ_INTERVAL,
                           "request_ifstates")

    def sibra_worker(self):
        while self.run_flag.is_set():
            start_time = SCIONTime.get_time()
            self.sibra_state.update_tick()
            sleep_interval(start_time, 1.0, "sibra_worker")

    def process_ifid_request(self, pkt, from_local):
        """
        After receiving IFID_PKT from neighboring router it is completed (by
        iface information) and passed to local BSes.

        :param ifid_packet: the IFID request packet to send.
        :type ifid_packet: :class:`lib.packet.scion.IFIDPacket`
        """
        if from_local:
            logging.error("Received IFID packet from local AS, dropping")
            return
        if pkt.addrs.dst.host != SVCType.BS:
            raise SCMPBadHost("Invalid SVC address: %s", pkt.addrs.dst.host)
        ifid_pld = pkt.get_payload().copy()
        # Forward 'alive' packet to all BSes (to inform that neighbor is alive).
        # BS must determine interface.
        ifid_pld.p.relayIF = self.interface.if_id
        try:
            bs_addrs = self.dns_query_topo(BEACON_SERVICE)
        except SCIONServiceLookupError as e:
            logging.error("Unable to deliver ifid packet: %s", e)
            raise SCMPUnknownHost
        for bs_addr in bs_addrs:
            pkt.set_payload(ifid_pld.copy())
            self.send(pkt, bs_addr)

    def get_srv_addr(self, service, pkt):
        """
        For a given service return a server address. Guarantee that all packets
        from the same source to a given service are sent to the same server.

        :param str service: Service to query for.
        :type pkt: :class:`lib.packet.scion.SCIONBasePacket`

        """
        addrs = self.dns_query_topo(service)
        addrs.sort()  # To not rely on order of DNS replies.
        return addrs[zlib.crc32(pkt.addrs.pack()) % len(addrs)]

    def process_pcb(self, pkt, from_bs):
        """
        Depending on scenario: a) send PCB to a local beacon server, or b) to
        neighboring router.

        :param beacon: The PCB.
        :type beacon: :class:`lib.packet.pcb.PathConstructionBeacon`
        :param bool from_bs: True, if the beacon was received from local BS.
        """
        pcb = pkt.get_payload()
        if from_bs:
            if self.interface.if_id != pcb.last_hof().egress_if:
                logging.error("Wrong interface set by BS.")
                return
            self.send(pkt, self.interface.to_addr, self.interface.to_udp_port)
        else:
            pcb.p.ifID = self.interface.if_id
            try:
                bs_addr = self.get_srv_addr(BEACON_SERVICE, pkt)
            except SCIONServiceLookupError as e:
                logging.error("Unable to deliver PCB: %s", e)
                raise SCMPUnknownHost
            self.send(pkt, bs_addr)

    def relay_cert_server_packet(self, spkt, from_local_as):
        """
        Relay packets for certificate servers.

        :param spkt: the SCION packet to forward.
        :type spkt: :class:`lib.packet.scion.SCIONPacket`
        :param bool from_local_as:
            whether or not the packet is from the local AS.
        """
        if from_local_as:
            addr = self.interface.to_addr
            port = self.interface.to_udp_port
        else:
            try:
                addr = self.get_srv_addr(CERTIFICATE_SERVICE, spkt)
            except SCIONServiceLookupError as e:
                logging.error("Unable to deliver cert packet: %s", e)
                raise SCMPUnknownHost
            port = SCION_UDP_EH_DATA_PORT
        self.send(spkt, addr, port)

    def fwd_sibra_service_pkt(self, spkt, _):
        """
        Forward SIBRA service packets to a SIBRA server.

        :param spkt: the SCION packet to forward.
        :type spkt: :class:`lib.packet.scion.SCIONPacket`
        """
        try:
            addr = self.get_srv_addr(SIBRA_SERVICE, spkt)
        except SCIONServiceLookupError as e:
            logging.error("Unable to deliver sibra service packet: %s", e)
            raise SCMPUnknownHost
        self.send(spkt, addr)

    def process_path_mgmt_packet(self, mgmt_pkt, from_local_as):
        """
        Process path management packets.

        :param mgmt_pkt: The path mgmt packet.
        :type mgmt_pkt: :class:`lib.packet.path_mgmt.PathMgmtPacket`
        :param bool from_local_as:
            whether or not the packet is from the local AS.
        """
        payload = mgmt_pkt.get_payload()
        if payload.PAYLOAD_TYPE == PMT.IFSTATE_INFO:
            # handle state update
            logging.debug("Received IFState update:\n%s",
                          str(mgmt_pkt.get_payload()))
            for p in payload.p.infos:
                self.if_states[p.ifID].update(IFStateInfo(p))
            return
        self.handle_data(mgmt_pkt, from_local_as)

    def process_revocation(self, spkt, from_local_as):
        pld = spkt.get_payload()
        logging.info("Processing revocation: %s", pld.info)
        # First, forward the packet as appropriate.
        self.handle_data(spkt, from_local_as)
        if from_local_as:
            return
        # Forward to local path server if we haven't recently.
        rev_token = pld.info.rev_token
        if (self.topology.path_servers and rev_token not in self.revocations):
            self.revocations[rev_token] = True
            try:
                ps = self.get_srv_addr(PATH_SERVICE, spkt)
            except SCIONServiceLookupError:
                logging.error("No local PS to forward revocation to.")
                raise SCMPUnknownHost
            ps_pkt = copy.deepcopy(spkt)
            ps_pkt.addrs.dst.isd_as = self.addr.isd_as
            ps_pkt.addrs.dst.host = ps
            # FIXME(kormat): disabling for now, as this doesn't currently work.
            # The dispatcher has no way to route the revocation scmp message to
            # the designated path server.
            logging.debug("DISABLED: Forwarding revocation to local PS: %s",
                          ps)
            # self.send(spkt, ps)
        self.handle_data(spkt, from_local_as)

    def send_revocation(self, spkt, if_id, ingress, path_incd):
        """
        Sends an interface revocation for 'if_id' along the path in 'spkt'.
        """
        logging.info("Interface %d is down. Issuing revocation.", if_id)
        # Check that the interface is really down.
        if_state = self.if_states[if_id]
        if self.if_states[if_id].is_active:
            logging.error("Interface %d appears to be up. Not sending " +
                          "revocation." % if_id)
            return

        assert if_state.rev_token, "Revocation token missing."

        rev_pkt = spkt.reversed_copy()
        rev_pkt.convert_to_scmp_error(self.addr,
                                      SCMPClass.PATH,
                                      SCMPPathClass.REVOKED_IF,
                                      spkt,
                                      if_id,
                                      ingress,
                                      if_state.rev_token,
                                      hopbyhop=True)
        if path_incd:
            rev_pkt.path.inc_hof_idx()
        rev_pkt.update()
        logging.debug("Revocation Packet:\n%s", rev_pkt)
        # FIXME(kormat): In some circumstances, this doesn't actually work, as
        # handle_data will try to send the packet to this interface first, and
        # then drop the packet as the interface is down.
        self.handle_data(rev_pkt, ingress, drop_on_error=True)

    def deliver(self, spkt, force=True):
        """
        Forwards the packet to the end destination within the current AS.

        :param spkt: The SCION Packet to forward.
        :type spkt: :class:`lib.packet.scion.SCIONPacket`
        :param bool force:
            If set, allow packets to be delivered locally that would otherwise
            be disallowed.
        """
        if not force and spkt.addrs.dst.isd_as != self.addr.isd_as:
            logging.error("Tried to deliver a non-local packet:\n%s", spkt)
            raise SCMPDeliveryNonLocal
        if len(spkt.path):
            hof = spkt.path.get_hof()
            if not force and hof.forward_only:
                raise SCMPDeliveryFwdOnly
            if hof.verify_only:
                raise SCMPNonRoutingHOF
        # Forward packet to destination.
        addr = spkt.addrs.dst.host
        if addr == SVCType.PS:
            # FIXME(PSz): that should be changed when replies are send as
            # standard data packets.
            # Send request to any path server.
            try:
                addr = self.get_srv_addr(PATH_SERVICE, spkt)
            except SCIONServiceLookupError as e:
                logging.error("Unable to deliver path mgmt packet: %s", e)
                raise SCMPUnknownHost
        elif addr == SVCType.SB:
            self.fwd_sibra_service_pkt(spkt, None)
            return
        self.send(spkt, addr)

    def verify_hof(self, path, ingress=True):
        """Verify freshness and authentication of an opaque field."""
        ts = path.get_iof().timestamp
        hof = path.get_hof()
        prev_hof = path.get_hof_ver(ingress=ingress)
        if int(SCIONTime.get_time()) <= ts + hof.exp_time * EXP_TIME_UNIT:
            if not hof.verify_mac(self.of_gen_key, ts, prev_hof):
                raise SCIONOFVerificationError(hof, prev_hof)
        else:
            raise SCIONOFExpiredError(hof)

    def _egress_forward(self, spkt):
        logging.debug("Forwarding to remote interface: %s:%s",
                      self.interface.to_addr, self.interface.to_udp_port)
        self.send(spkt, self.interface.to_addr, self.interface.to_udp_port)

    def handle_data(self, spkt, from_local_as, drop_on_error=False):
        """
        Main entry point for data packet handling.

        :param spkt: The SCION Packet to process.
        :type spkt: :class:`lib.packet.scion.SCIONPacket`
        :param from_local_as:
            Whether or not the packet is from the local AS.
        """
        ingress = not from_local_as
        try:
            self._process_data(spkt, ingress, drop_on_error)
        except SCIONOFVerificationError as e:
            logging.error(
                "Dropping packet due to incorrect MAC.\n"
                "Header:\n%s\nInvalid OF: %s\nPrev OF: %s", spkt, e.args[0],
                e.args[1])
            raise SCMPBadMAC from None
        except SCIONOFExpiredError as e:
            logging.error(
                "Dropping packet due to expired OF.\n"
                "Header:\n%s\nExpired OF: %s", spkt, e)
            raise SCMPExpiredHOF from None
        except SCIONPacketHeaderCorruptedError:
            logging.error(
                "Dropping packet due to invalid header state.\n"
                "Header:\n%s", spkt)
        except SCIONInterfaceDownException:
            logging.debug("Dropping packet due to interface being down")
            pass

    def _process_data(self, spkt, ingress, drop_on_error):
        path = spkt.path
        if len(spkt) > self.topology.mtu:
            # FIXME(kormat): ignore this check for now, as PCB packets are often
            # over MTU, it's just that udp-overlay handles fragmentation for us.
            # Once we have TCP/SCION, this check should be re-instated.
            # This also needs to look at the specific MTU for the relevant link
            # if on egress.
            #  raise SCMPOversizePkt("Packet larger than mtu", mtu)
            pass
        self.verify_hof(path, ingress=ingress)
        hof = spkt.path.get_hof()
        if hof.verify_only:
            raise SCMPNonRoutingHOF
        # FIXME(aznair): Remove second condition once PathCombinator is less
        # stupid.
        if (spkt.addrs.dst.isd_as == self.addr.isd_as
                and spkt.path.is_on_last_segment()):
            self.deliver(spkt)
            return
        if ingress:
            fwd_if, path_incd = self._calc_fwding_ingress(spkt)
        else:
            fwd_if = path.get_fwd_if()
            path_incd = False
        try:
            if_addr = self.ifid2er[fwd_if].addr
        except KeyError:
            # So that the error message will show the current state of the
            # packet.
            spkt.update()
            logging.error("Cannot forward packet, fwd_if is invalid (%s):\n%s",
                          fwd_if, spkt)
            raise SCMPBadIF(fwd_if) from None
        if not self.if_states[fwd_if].is_active:
            if drop_on_error:
                logging.debug("IF is down, but drop_on_error is set, dropping")
                return
            self.send_revocation(spkt, fwd_if, ingress, path_incd)
            return
        if ingress:
            logging.debug("Sending to IF %s (%s)", fwd_if, if_addr)
            self.send(spkt, if_addr)
        else:
            path.inc_hof_idx()
            self._egress_forward(spkt)

    def _calc_fwding_ingress(self, spkt):
        path = spkt.path
        hof = path.get_hof()
        incd = False
        if hof.xover:
            path.inc_hof_idx()
            incd = True
        return path.get_fwd_if(), incd

    def _needs_local_processing(self, pkt):
        if len(pkt.path) == 0:
            if pkt.addrs.dst.host.TYPE == AddrType.SVC:
                # Always process packets with SVC destinations and no path
                return True
            elif pkt.addrs.dst == self.addr:
                # Destination is the internal address of this router.
                return True
            raise SCMPPathRequired
        if (pkt.addrs.dst.isd_as == self.addr.isd_as
                and pkt.addrs.dst.host.TYPE == AddrType.SVC):
            # Destination is a local SVC address.
            return True
        return False

    def _process_flags(self, flags, pkt, from_local_as):
        """
        Go through the flags set by hop-by-hop extensions on this packet.
        :returns:
        """
        process = False
        # First check if any error or no_process flags are set
        for (flag, *args) in flags:
            if flag == RouterFlag.ERROR:
                logging.error("%s", args[0])
                return True, False
            elif flag == RouterFlag.NO_PROCESS:
                return True, False
        # Now check for other flags
        for (flag, *args) in flags:
            if flag == RouterFlag.FORWARD:
                if from_local_as:
                    self._process_fwd_flag(pkt)
                else:
                    self._process_fwd_flag(pkt, args[0])
                return True, False
            elif flag in (RouterFlag.DELIVER, RouterFlag.FORCE_DELIVER):
                self._process_deliver_flag(pkt, flag)
                return True, False
            elif flag == RouterFlag.PROCESS_LOCAL:
                process = True
        return False, process

    def _process_fwd_flag(self, pkt, ifid=None):
        if ifid is None:
            logging.debug("Packet forwarded over link by extension")
            self._egress_forward(pkt)
            return
        if ifid == 0:
            logging.error(
                "Extension asked to forward this to interface 0:\n%s", pkt)
            return
        next_hop = self.ifid2er[ifid].addr
        logging.debug("Packet forwarded by extension via %s", next_hop)
        self.send(pkt, next_hop)

    def _process_deliver_flag(self, pkt, flag):
        if (flag == RouterFlag.DELIVER
                and pkt.addrs.dst.isd_as != self.addr.isd_as):
            logging.error(
                "Extension tried to deliver this locally, but this "
                "is not the destination ISD-AS:\n%s", pkt)
            return
        logging.debug("Packet delivered by extension")
        self.deliver(pkt)

    def handle_request(self, packet, _, from_local_socket=True, sock=None):
        """
        Main routine to handle incoming SCION packets.

        :param bytes packet: The incoming packet to handle.
        :param tuple sender: Tuple of sender IP, port.
        :param bool from_local_socket:
            True, if the packet was received on the local socket.
        """
        from_local_as = from_local_socket
        pkt = self._parse_packet(packet)
        if not pkt:
            return
        if pkt.ext_hdrs:
            logging.debug("Got packet (from_local_as? %s):\n%s", from_local_as,
                          pkt)
        try:
            flags = self.handle_extensions(pkt, True, from_local_as)
        except SCMPError as e:
            self._scmp_validate_error(pkt, e)
            return
        stop, needs_local = self._process_flags(flags, pkt, from_local_as)
        if stop:
            logging.debug("Stopped processing")
            return
        try:
            needs_local |= self._needs_local_processing(pkt)
        except SCMPError as e:
            self._scmp_validate_error(pkt, e)
            return
        if needs_local:
            try:
                pkt.parse_payload()
            except SCIONBaseError:
                log_exception("Error parsing payload:\n%s" % hex_str(packet))
                return
            handler = self._get_handler(pkt)
        else:
            # It's a normal packet, just forward it.
            handler = self.handle_data
        logging.debug(
            "handle_request (from_local_as? %s):"
            "\n  %s\n  %s\n  handler: %s", from_local_as, pkt.cmn_hdr,
            pkt.addrs, handler)
        if not handler:
            return
        try:
            handler(pkt, from_local_as)
        except SCMPError as e:
            self._scmp_validate_error(pkt, e)
        except SCIONBaseError:
            log_exception("Error handling packet: %s" % pkt)
Ejemplo n.º 12
0
 def __init__(
     self,
     server_id,
     conf_dir,
 ):
     """
     :param str server_id: server identifier.
     :param str conf_dir: configuration directory.
     """
     super().__init__(
         server_id,
         conf_dir,
     )
     self.interface = None
     for edge_router in self.topology.get_all_edge_routers():
         if edge_router.addr == self.addr.host:
             self.interface = edge_router.interface
             break
     assert self.interface is not None
     logging.info("Interface: %s", self.interface.__dict__)
     self.of_gen_key = PBKDF2(self.config.master_as_key, b"Derive OF Key")
     self.sibra_key = PBKDF2(self.config.master_as_key, b"Derive SIBRA Key")
     self.if_states = defaultdict(InterfaceState)
     self.revocations = ExpiringDict(1000, self.FWD_REVOCATION_TIMEOUT)
     self.pre_ext_handlers = {
         SibraExtBase.EXT_TYPE:
         self.handle_sibra,
         TracerouteExt.EXT_TYPE:
         self.handle_traceroute,
         ExtHopByHopType.SCMP:
         self.handle_scmp,
         HORNETPlugin.EXT_TYPE:
         HORNETPlugin(self.id, conf_dir, self.config.master_as_key,
                      self.addr, self.interface).pre_routing
     }
     self.post_ext_handlers = {
         SibraExtBase.EXT_TYPE: False,
         TracerouteExt.EXT_TYPE: False,
         ExtHopByHopType.SCMP: False,
         HORNETPlugin.EXT_TYPE: False
     }
     self.sibra_state = SibraState(
         self.interface.bandwidth, "%s#%s -> %s" %
         (self.addr.isd_as, self.interface.if_id, self.interface.isd_as))
     self.CTRL_PLD_CLASS_MAP = {
         PayloadClass.PCB: {
             PCBType.SEGMENT: self.process_pcb
         },
         PayloadClass.IFID: {
             IFIDType.PAYLOAD: self.process_ifid_request
         },
         PayloadClass.CERT:
         defaultdict(lambda: self.relay_cert_server_packet),
         PayloadClass.PATH:
         defaultdict(lambda: self.process_path_mgmt_packet),
         PayloadClass.SIBRA: {
             SIBRAPayloadType.EMPTY: self.fwd_sibra_service_pkt
         },
     }
     self.SCMP_PLD_CLASS_MAP = {
         SCMPClass.PATH: {
             SCMPPathClass.REVOKED_IF: self.process_revocation
         },
     }
     self._remote_sock = UDPSocket(
         bind=(str(self.interface.addr), self.interface.udp_port),
         addr_type=AddrType.IPV4,
     )
     self._socks.add(self._remote_sock, self.handle_recv)
     logging.info("IP %s:%d", self.interface.addr, self.interface.udp_port)