Example #1
0
 def test_block(self, init):
     inst = UDPSocket()
     inst.sock = create_mock(["recvfrom"])
     # Call
     ntools.eq_(inst.recv(), inst.sock.recvfrom.return_value)
     # Tests
     inst.sock.recvfrom.assert_called_once_with(SCION_BUFLEN, 0)
Example #2
0
 def test_basic(self, init):
     inst = UDPSocket()
     inst.sock = create_mock(["sendto"])
     # Call
     inst.send("data", "dst")
     # Tests
     inst.sock.sendto.assert_called_once_with("data", "dst")
Example #3
0
 def test_nonblock(self, init):
     inst = UDPSocket()
     inst.sock = create_mock(["recvfrom"])
     # Call
     inst.recv(block=False)
     # Tests
     inst.sock.recvfrom.assert_called_once_with(SCION_BUFLEN,
                                                socket.MSG_DONTWAIT)
Example #4
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)
Example #5
0
 def test_intr(self, init):
     inst = UDPSocket()
     inst.sock = create_mock(["recvfrom"])
     inst.sock.recvfrom.side_effect = (
         InterruptedError, InterruptedError, "data")
     # Call
     ntools.eq_(inst.recv(), "data")
     # Tests
     ntools.eq_(inst.sock.recvfrom.call_count, 3)
Example #6
0
 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)
Example #7
0
 def _check_error(self, errno, excp, init, logging):
     inst = UDPSocket()
     inst.sock = create_mock(["sendto"])
     inst.sock.sendto.side_effect = OSError(errno)
     # Call
     if excp:
         ntools.assert_raises(excp, inst.send, "data", "dst")
     else:
         inst.send("data", "dst")
     # Tests
     ntools.ok_(logging.called)
Example #8
0
 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)
Example #9
0
 def test_minimal(self, socket_, bind):
     socket.return_value = create_mock(["setsockopt"])
     # Call
     UDPSocket()
     # Tests
     socket_.assert_called_once_with(socket.AF_INET6, socket.SOCK_DGRAM)
     ntools.assert_false(bind.called)
Example #10
0
 def test_full(self, socket_, bind):
     socket_.return_value = create_mock(["setsockopt"])
     # Call
     inst = UDPSocket(bind=("addr", "port"), addr_type=AddrType.IPV4)
     # Tests
     ntools.eq_(inst._addr_type, AddrType.IPV4)
     socket_.assert_called_once_with(socket.AF_INET, socket.SOCK_DGRAM)
     ntools.assert_is_none(inst.port)
     bind.assert_called_once_with(inst, "addr", "port")
 def __init__(self, kbase, topo_file, loc_file):
     """
     Creates and initializes an instance of the lookup service.
     :param kbase: Socket knowledge-base object.
     :type kbase: SocketKnowledgeBase
     """
     self.kbase = kbase
     self.topology_file = topo_file
     self.locations_file = loc_file
     # Create a UDP socket
     self.sock = UDPSocket(SERVER_ADDRESS, AddrType.IPV4)
     # Bind the socket to the port
     logging.debug("Socket stats service starting up on %s port %s",
                   SERVER_ADDRESS[0], SERVER_ADDRESS[1])
     self.service = threading.Thread(target=thread_safety_net,
                                     args=(self._run, ),
                                     name="stats_lookup",
                                     daemon=True)
     self.service.start()
def test_clear_kbase():
    """
    Creates a sample clear knowledge-base request, sends it to the
    UDP server and reads the response returned.
    """
    logging.info('Starting the clear test')
    # Open up a UDP socket
    sock = UDPSocket(None, AddrType.IPV4)

    req = {"version": "0.1",
           "command": "CLEAR"}

    send_req_and_read_resp(sock, req)
def get_ISD_whitelist():
    """
    Creates an ISD whitelist request, sends it to the UDP server.
    """

    logging.info('Starting the Get ISD whitelist test')
    # Create a UDP socket
    sock = UDPSocket(None, AddrType.IPV4)

    req = {"version": "0.1",
           "command": "GET_ISD_WHITELIST"}

    return send_req_and_read_resp(sock, req)
def test_topology_lookup():
    """
    Creates a sample topology request, sends it to the UDP server
    and reads the response.
    """

    logging.info('Starting the topology request test')
    # Create a UDP socket
    sock = UDPSocket(None, AddrType.IPV4)

    req = {"version": "0.1",
           "command": "TOPO"}

    send_req_and_read_resp(sock, req)
def test_locations_lookup():
    """
    Creates a sample default locations request, sends it to the UDP
    server and reads the response.
    """

    logging.info('Starting the locations request test')
    # Create a UDP socket
    sock = UDPSocket(None, AddrType.IPV4)

    req = {"version": "0.1",
           "command": "LOCATIONS"}

    send_req_and_read_resp(sock, req)
def test_list():
    """
    Creates a sample list request, sends it to the UDP server and
    reads the response.
    """
    logging.info('Starting the list test')
    # Open up a UDP socket
    sock = UDPSocket(None, AddrType.IPV4)

    req = {"version": "0.1",
           "command": "LIST"}

    raw_resp = send_req_and_read_resp(sock, req)
    return extract_from_json(raw_resp)
def test_ISD_endpoints_lookup():
    """
    Creates a get ISD end-points request, sends it to the
    UDP server and reads the response.
    """

    logging.info('Starting the get ISD end-points test')
    # Create a UDP socket
    sock = UDPSocket(None, AddrType.IPV4)

    req = {"version": "0.1",
           "command": "GET_ISD_ENDPOINTS"}

    send_req_and_read_resp(sock, req)
Example #18
0
 def __init__(self, kbase, topo_file, loc_file):
     """
     Creates and initializes an instance of the lookup service.
     :param kbase: Socket knowledge-base object.
     :type kbase: SocketKnowledgeBase
     """
     self.kbase = kbase
     self.topology_file = topo_file
     self.locations_file = loc_file
     # Create a UDP socket
     self.sock = UDPSocket(SERVER_ADDRESS, AddrType.IPV4)
     # Bind the socket to the port
     logging.debug("Socket stats service starting up on %s port %s",
                   SERVER_ADDRESS[0], SERVER_ADDRESS[1])
     # Print the disclaimer
     logging.info("DISCLAIMER: This stats gathering service is currently "
                  "used only as a demonstrative tool. If you wish to use "
                  "it in a production environment, then proper access "
                  "control mechanisms must be implemented.")
     self.service = threading.Thread(target=thread_safety_net,
                                     args=(self._run, ),
                                     name="stats_lookup",
                                     daemon=True)
     self.service.start()
def test_clear_ISD_whitelist():
    """
    Creates a ISD whitelist request, sends it to the UDP server.
    """

    logging.info('Starting the ISD whitelist test')
    # Create a UDP socket
    sock = UDPSocket(None, AddrType.IPV4)

    req = {"version": "0.1",
           "command": "ISD_WHITELIST",
           "isds": []}

    send_req_and_read_resp(sock, req)
    raw_resp = get_ISD_whitelist()
    res = extract_from_json(raw_resp)
    assert(res == [])
def test_lookup(res):
    """
    Creates a sample look-up request, sends it to the UDP server and
    reads the response.
    """
    if res is None or len(res) == 0:
        return

    logging.info('Starting the look-up test')
    item = res[0]
    # Create a UDP socket
    sock = UDPSocket(None, AddrType.IPV4)

    req = {"version": "0.1",
           "command": "LOOKUP",
           "conn_id": item[0],
           "req_type": item[1],
           "res_name": item[2]}

    logging.info('Sending LOOKUP request %s', req)
    send_req_and_read_resp(sock, req)
Example #21
0
    def test(self):
        """
        Bandwidth test method. Obtains a path to (2, 26) and sends PACKETS_NO
        packets (each with PAYLOAD_SIZE long payload) to a host in (2, 26).
        """
        addr = haddr_parse("IPV4", "127.1.19.254")
        conf_dir = "%s/ISD1/AD19/endhost" % GEN_PATH
        sender = SCIONDaemon.start(conf_dir, addr)

        paths = sender.get_paths(2, 26)
        self.assertTrue(paths)

        rcv_sock = UDPSocket(bind=("127.2.26.254", 0, "Bw test receiver"),
                             addr_type=AddrType.IPV4)

        logging.info("Starting the receiver.")
        recv_t = threading.Thread(
            target=thread_safety_net, args=(self.receiver, rcv_sock),
            name="BwT.receiver")
        recv_t.start()

        payload = PayloadRaw(b"A" * PAYLOAD_SIZE)
        spkt = sender._build_packet(
            haddr_parse("IPV4", "127.2.26.254"), dst_isd=2, dst_ad=26,
            dst_port=rcv_sock.port, payload=payload, path=paths[0])
        (next_hop, port) = sender.get_first_hop(spkt)
        assert next_hop is not None
        logging.info("Sending %d payload bytes (%d packets x %d bytes )" %
                     (PACKETS_NO * PAYLOAD_SIZE, PACKETS_NO, PAYLOAD_SIZE))
        for _ in range(PACKETS_NO):
            sender.send(spkt, next_hop, port)
            time.sleep(SLEEP)
        logging.info("Sending finished")

        recv_t.join()
        if self.rate < 10.0:
            sys.exit(0)
        else:
            sys.exit(int(self.rate))
Example #22
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)
Example #23
0
 def _create_socket(self, addr):
     # Use UDPSocket directly to bypass the overhead of the dispatcher.
     return UDPSocket(bind=(str(addr.host), 0, ""),
                      addr_type=addr.host.TYPE)
Example #24
0
 def _setup(self):
     inst = UDPSocket()
     inst.sock = create_mock(["bind", "getsockname"])
     inst.sock.getsockname.return_value = ["addr", 5353]
     return inst
Example #25
0
class KnowledgeBaseLookupService(object):
    """
    This class starts up a UDP server which binds to SERVICE_PORT and
    responds to incoming UDP datagrams which contain lookup requests.
    It pulls the stats from SocketKnowledgeBase object which is also
    the creator of this object.
    """
    def __init__(self, kbase, topo_file, loc_file):
        """
        Creates and initializes an instance of the lookup service.
        :param kbase: Socket knowledge-base object.
        :type kbase: SocketKnowledgeBase
        """
        self.kbase = kbase
        self.topology_file = topo_file
        self.locations_file = loc_file
        # Create a UDP socket
        self.sock = UDPSocket(SERVER_ADDRESS, AddrType.IPV4)
        # Bind the socket to the port
        logging.debug("Socket stats service starting up on %s port %s",
                      SERVER_ADDRESS[0], SERVER_ADDRESS[1])
        # Print the disclaimer
        logging.info("DISCLAIMER: This stats gathering service is currently "
                     "used only as a demonstrative tool. If you wish to use "
                     "it in a production environment, then proper access "
                     "control mechanisms must be implemented.")
        self.service = threading.Thread(target=thread_safety_net,
                                        args=(self._run, ),
                                        name="stats_lookup",
                                        daemon=True)
        self.service.start()

    def _run(self):
        """
        Serves the incoming requests serially, one by one.
        This can be parallelized in the future, currently there is no
        need to process parallel as only one browser extension
        will talk to this service and send requests one by one.
        """
        while True:
            try:
                self._serve_query()
            except OSError:
                break

    def _serve_query(self):
        """
        Reads and parses a query, looks it up and returns the result.
        """
        req_raw, addr = self._recv_data()
        if req_raw is None or len(req_raw) < 4 or addr is None:
            return
        req_len = struct.unpack("!I", req_raw[:4])[0]
        if req_len != len(req_raw[4:]):
            logging.error('Request length does not match the data length')
            return
        try:
            req_str = req_raw[4:].decode("utf-8")
            request = json.loads(req_str)
        except (UnicodeDecodeError, json.JSONDecodeError) as e:
            logging.error('Error decoding request: %s' % e)
            return

        logging.debug('Length of the request = %d' % req_len)
        logging.debug('Received request %s' % req_raw[4:4 + req_len])
        assert (isinstance(request, dict))

        try:
            cmd = request['command']
        except KeyError as e:
            logging.error('Key error while parsing request: %s' % e)
            return
        assert (isinstance(cmd, str))

        if cmd == 'LIST':
            resp = self.kbase.list()
        elif cmd == 'LOOKUP':
            try:
                conn_id = request['conn_id']
                req_type = request['req_type']
                res_name = request['res_name']
            except KeyError as e:
                logging.error('Key error while parsing LOOKUP req: %s' % e)
                return
            assert (isinstance(req_type, str))
            resp = self.kbase.lookup(conn_id, req_type, res_name)
        elif cmd == 'CLEAR':
            resp = self.kbase.clear()
        elif cmd == 'TOPO':
            resp = self._get_topology()
        elif cmd == 'LOCATIONS':
            resp = self._get_locations()
        elif cmd == 'ISD_WHITELIST':
            try:
                isds = request['isds']
            except KeyError as e:
                logging.error('Key error in parsing ISD_WHITELIST req: %s' % e)
                return
            assert (isinstance(isds, list))
            resp = self._handle_set_ISD_whitelist(isds)
        elif cmd == 'GET_ISD_WHITELIST':
            resp = self._handle_get_ISD_whitelist()
        elif cmd == 'GET_ISD_ENDPOINTS':
            resp = self._handle_get_ISD_endpoints()
        else:
            logging.error('Unsupported command: %s', cmd)
            return

        assert ((isinstance(resp, dict) or isinstance(resp, list)))
        self._send_response(resp, addr)

    def _recv_data(self):
        """
        Reads the data in from the socket.
        :returns:
            Tuple of (`bytes`, (`address`)) containing the data and
            remote address.
        """
        logging.debug('Waiting to receive the request length')
        try:
            req_raw, addr = self.sock.recv()
        except OSError as e:
            logging.error('Error while reading from socket: %s' % e)
            raise OSError("Can't read from the socket: It is dead.")
        logging.debug('Request = %s' % req_raw)
        return req_raw, addr

    def _send_response(self, resp, addr):
        """
        Encodes the response object (dict or list) into JSON and sends it to
        the given address
        :param resp: Response to be sent to the client.
        :type resp: dict or list
        :param addr: Address to send the response to.
        :type addr: AddrType.IPV4
        """
        # prepare the response
        result = []
        try:
            resp_str = json.dumps(resp)
            resp_bytes = resp_str.encode('utf-8')
        except ValueError as e:
            logging.error('Error while encoding JSON: %s' % e)
            return

        # the number of bytes contained in JSON response
        logging.debug("Response length is %d" % len(resp_bytes))
        resp_len = struct.pack("!I", len(resp_bytes))
        result.append(resp_len)
        result.append(resp_bytes)
        try:
            # send the response
            self.sock.send(b''.join(result), addr)
        except OSError as e:
            logging.error('Error while sending response: %s' % e)
            raise OSError("Can't write to the socket: It is dead.")

    def _get_topology(self):
        """
        Reads in the topology file and serves the relevant part of that
        to the visualization extension.
        :returns: A list of links extracted from the topology file.
        :rtype: list
        """
        with open(self.topology_file, 'r') as stream:
            try:
                topo_dict = yaml.load(stream)
                logging.debug('Topology: %s' % topo_dict)
                return topo_dict['links']
            except (yaml.YAMLError, KeyError) as e:
                logging.error('Error while reading the topology YAML: %s' % e)
                return []

    def _get_locations(self):
        """
        Reads in the default locations file and serves the relevant part of that
        to the visualization extension.
        :returns: A dictionary of AS Name to Country Code matching.
        :rtype: dict
        """
        with open(self.locations_file, 'r') as stream:
            try:
                locations_dict = yaml.load(stream)
                logging.debug('Locations: %s' % locations_dict)
                return locations_dict['locations']
            except (yaml.YAMLError, KeyError) as e:
                logging.error('Error while reading the locations YAML: %s' % e)
                return {}

    def _handle_set_ISD_whitelist(self, isds):
        """
        Lets the kbase know of which ISDs should be whitelisted.
        :param isds: List of ISDs (numbers)
        :type isds: list
        :returns: A dictionary indicating the result.
        :rtype: dict
        """
        return self.kbase.set_ISD_whitelist(isds)

    def _handle_get_ISD_whitelist(self):
        """
        Queries the kbase and returns which ISDs are whitelisted.
        :returns: A list (potentially empty) containing the whitelisted ISDs.
        :rtype: list
        """
        return self.kbase.get_ISD_whitelist()

    def _handle_get_ISD_endpoints(self):
        """
        Gets the source and target ISD end-points and returns them as a
        dictionary.
        :returns: a dictionary containing source_ISD_AS and target_ISD_AS
        :rtype: dict
        """
        result = {}
        result["source_ISD_AS"] = list(self.kbase.source_ISD_AS)
        result["target_ISD_AS"] = list(self.kbase.target_ISD_AS)
        return result
Example #26
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)