def client_coro(loop, server_host, server_port): client = RadiusClient(loop, default_server=server_host, default_port=server_port) request = packet.AccountingStatusServer(SHARED_SECRET) future = yield from client.send_packet(request) response, response_time = yield from future self.assertTrue(response.code == packet.ACCOUNTING_RESPONSE) self.assertTrue(len(response.attributes) == 0) client.close()
def client_coro(loop, server_host, server_port): client = RadiusClient(loop, default_server=server_host, default_port=server_port) request = packet.StatusServer(SHARED_SECRET) request.attributes.extend(('NAS-Identifier', 'test_nas'), ) future = yield from client.send_packet(request) response, response_time = yield from future self.assertTrue(response.code == packet.ACCESS_ACCEPT) self.assertTrue(len(response.attributes) == 0) client.close()
def client_coro(loop, server_host, server_port): client = RadiusClient(loop, default_server=server_host, default_port=server_port) request = packet.AccessRequest(SHARED_SECRET) request.attributes = ( ('User-Name', 'username'), ('User-Password', 'password'), ) future = yield from client.send_packet(request) response, response_time = yield from future self.assertTrue(response.code == packet.ACCESS_REJECT) future = yield from client.send_packet(request, identifier=0) response, response_time = yield from future self.assertTrue(response.code == packet.ACCESS_REJECT) client.close()
def client_coro(loop, server_host, server_port, server): client = RadiusClient(loop) request = packet.AccountingRequest(SHARED_SECRET) request.attributes.extend(('User-Name', 'username'), ('Acct-Session-Id', 'session-test'), ('Acct-Status-Type', 'Interim-Update')) future = yield from client.send_packet(request, remote_host=server_host, remote_port=server_port, retries=3, timeout=1) with self.assertRaises(asyncio.TimeoutError): response, response_time = yield from future self.assertIsInstance(server.last_exc, RadiusResponseError) self.assertEqual(client.stat.get('requests'), 1) self.assertEqual(client.stat.get('responses'), 0) self.assertEqual(client.stat.get('no_responses'), 1) self.assertEqual(client.stat.get('timeouts'), 1) client.close()
def client_coro(loop, server_host, server_port, server): client = RadiusClient(loop, client_identifier='test-client') request = packet.AccountingRequest(SHARED_SECRET) request.attributes.extend(('User-Name', 'username'), ('Acct-Session-Id', 'session-test'), ('Acct-Status-Type', 'Start')) future = yield from client.send_packet(request, remote_host=server_host, remote_port=server_port) response, response_time = yield from future self.assertEqual(request.attributes.get('NAS-Identifier'), 'test-client') self.assertTrue(response.code == packet.ACCOUNTING_RESPONSE) self.assertIn('session-test', server.acct_sessions) request = packet.AccountingRequest(SHARED_SECRET) request.attributes.extend(('User-Name', 'username'), ('Acct-Session-Id', 'session-test'), ('Acct-Status-Type', 'Stop')) future = yield from client.send_packet(request, remote_host=server_host, remote_port=server_port) response, response_time = yield from future self.assertTrue(response.code == packet.ACCOUNTING_RESPONSE) self.assertNotIn('session-test', server.acct_sessions) request = packet.AccountingRequest(SHARED_SECRET) request.attributes.extend(('User-Name', 'username'), ('Acct-Session-Id', 'session-test'), ('Acct-Status-Type', 'Stop')) #with self.assertRaises(asyncio.TimeoutError): future = yield from client.send_packet(request, remote_host=server_host, remote_port=server_port) with self.assertRaises(asyncio.TimeoutError): response, response_time = yield from future self.assertIsInstance(server.last_exc, RadiusResponseError) client.close()
class AbstractRadiusServer(RadiusService): def __init__(self, host=DEFAULT_HOST, auth_port=DEFAULT_AUTH_PORT, acc_port=DEFAULT_ACC_PORT, loop=None, init_dac_client=True): """ Create class instance or raise RadiusServerError if cant create it """ # Verifying param `host`, it must be IP-address or hostname # Raise ServerError if host is not IP-address and not hostname self.logger = logging.getLogger(self.__class__.__name__) self.loop = loop or asyncio.new_event_loop() self.host = self.__validate_host(host) self.auth_port = self.__validate_port(auth_port) self.acc_port = self.__validate_port(acc_port) self.process_id = os.getpid() self.parent_process_id = os.getppid() self.__is_stoped = False self.__start_time = 0 self.__stop_time = 0 self.logger.debug("Used {} as event_loop".format( class_fullname(self.loop))) # Get methods decorated with @periodic_task and create for it PeriodicTask instances self.periodic_tasks = [] for attr_name in dir(self): attr = getattr(self, attr_name) if inspect.ismethod(attr) and hasattr( attr, 'is_periodic_task') and attr.is_periodic_task: self.periodic_tasks.append( PeriodicTask(self.loop, attr, delay=attr.delay)) self.__auth_transport = None self.__auth_proto = None self.__acc_transport = None self.__acc_proto = None if init_dac_client: self.__dac = RadiusClient(loop=self.loop) else: self.__dac = None @staticmethod def __validate_host(host): if host == '0.0.0.0': validated_host = host try: validated_host = IPv4Address(host) except ValueError: try: validated_host = socket.gethostbyname(host) except: raise RadiusServerError( "Bad value of host '{}', use IPv4 address or hostname". format(host)) return str(validated_host) @staticmethod def __validate_port(port): if port is None: return None try: validated_port = int(port) except (ValueError, TypeError): raise RadiusServerError("Bad value of UDP port '{}'".format(port)) return validated_port @property def uptime(self): return self.__stop_time - self.__start_time def get_event_loop(self): return self.loop def __initialize_udp_endpoints(self): try: if asyncio.iscoroutinefunction(self.on_auth_packet): self.loop.run_until_complete(self.on_auth_packet(None)) else: self.on_auth_packet(None) except NotImplementedError: self.logger.warning( "Method `on_auth_packet` is not implemented, auth port wouldn't be listen" ) self.auth_port = None except Exception: pass try: if asyncio.iscoroutinefunction(self.on_acct_packet): self.loop.run_until_complete(self.on_acct_packet(None)) else: self.on_acct_packet(None) except NotImplementedError: self.logger.warning( "Method `on_acct_packet` is not implemented, acct port wouldn't be listen" ) self.acc_port = None except Exception: pass any_port_is_listen = False if self.auth_port is not None: # Listen UDP socket for auth try: self.__auth_transport, self.__auth_proto = self.loop.run_until_complete( self.loop.create_datagram_endpoint( lambda: RadiusAuthProtocol(self), local_addr=(self.host, self.auth_port))) any_port_is_listen = True except OSError as e: raise RadiusServerError( "Cant listen UDP socket for auth on port {}, {!s}".format( self.auth_port, e)) if self.acc_port is not None: # Listen UDP socket for acc try: self.__acc_transport, self.__acc_proto = self.loop.run_until_complete( self.loop.create_datagram_endpoint( lambda: RadiusAccountingProtocol(self), local_addr=(self.host, self.acc_port))) any_port_is_listen = True except OSError as e: raise RadiusServerError( "Cant listen UDP socket for acc on port {}, {!s}".format( self.acc_port, e)) if not any_port_is_listen: raise RadiusServerError("Server is not listen any port") def __close_udp_endpoints(self): # Close and wait_for_close UDP socket for auth if self.__auth_transport is not None: self.__auth_transport.close() self.__auth_proto.wait_for_close() # Close and wait_for_close UDP socket for acc if self.__acc_transport is not None: self.__acc_transport.close() self.__acc_proto.wait_for_close() def __schedule_periodic_task(self): # Starting periodic tasks for item in self.periodic_tasks: item.start() def __unschedule_periodic_task(self): # Stop periodic tasks for item in self.periodic_tasks: item.stop() def shutdown(self): self.__is_stoped = True @asyncio.coroutine def __serve(self, future=None): try: if future is not None: yield from future else: while not self.__is_stoped: yield from asyncio.sleep(1) except asyncio.CancelledError: pass except Exception as exc: self.register_exception(exc) finally: pass async_cancel_tasks(self.loop) @asyncio.coroutine def on_startup(self): return @asyncio.coroutine def on_shutdown(self, *args, **kwargs): return def close_dac(self): # Close DAC client if isinstance(self.__dac, RadiusClient): self.__dac.close() def run(self, future=None): # Log server process ID self.logger.info("Starting '{}' with PID {}(parent ID: {})".format( self.__class__.__name__, self.process_id, self.parent_process_id)) self.__start_time = self.loop.time() # Event loop is not running self.loop.run_until_complete(self.on_startup()) if not self.__is_stoped: self.__initialize_udp_endpoints() self.__schedule_periodic_task() self.loop.add_signal_handler(signal.SIGTERM, self.shutdown) if future is not None: if asyncio.iscoroutine(future): future = asyncio.ensure_future(future, loop=self.loop) # Start server event loop try: self.loop.run_until_complete(self.__serve(future)) except KeyboardInterrupt: self.logger.info("Server '{}' process was interrupted".format( self.__class__.__name__)) if future is not None: future.cancel() self.shutdown() except asyncio.CancelledError: self.logger.info("Server '{}' process was interrupted".format( self.__class__.__name__)) if future is not None: future.cancel() self.shutdown() self.loop.remove_signal_handler(signal.SIGTERM) cancel_tasks(self.loop) self.loop.run_until_complete(self.on_shutdown()) self.close_dac() self.__unschedule_periodic_task() self.__close_udp_endpoints() self.__stop_time = self.loop.time() # Stop event loop if not self.loop.is_running(): self.loop.stop() if not self.loop.is_closed(): self.loop.close() self.logger.info("'{}' was stopped, uptime - {:.2f} sec".format( self.__class__.__name__, self.uptime)) async def send_to_das(self, request, das_host, das_port=DEFAULT_DAS_PORT, **kwargs): """ Async Send Disconnect-Request or CoA-Request to DAS and async return response from DAS """ if not isinstance(request, (packet.DisconnectRequest, packet.CoARequest)): raise RadiusServerError( "You can send to DAS packet only of type DisconnectRequest or CoaRequest" ) if 'NAS-IP-Address' not in request.attributes and 'NAS-Identifier' not in request.attributes: request.attributes.extend(('NAS-IP-Address', str(das_host)), ) # TODO: Разобраться с генерацией authenticator-а bytes(request) response_future = await self.__dac.send_packet(request, das_host, das_port, **kwargs) self.logger.info("Sent '{0!s}' to '{1}:{2}'".format( request, das_host, das_port)) self.logger.debug(packet.packet_view(request)) response, response_time = await response_future self.logger.info( "Got '{0!s}' from '{1}:{2}' (processed by {3:.4f} ms)".format( response, das_host, das_port, response_time)) self.logger.debug(packet.packet_view(response)) return response