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)
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")
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)
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 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)
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 _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)
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 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)
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)
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)
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))
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)
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)
def _setup(self): inst = UDPSocket() inst.sock = create_mock(["bind", "getsockname"]) inst.sock.getsockname.return_value = ["addr", 5353] return inst
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
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)