97 lines
2.7 KiB
Python
97 lines
2.7 KiB
Python
# Copyright 2020 Istvan Ruzman
|
|
# SPDX-License-Identifier: MIT OR Apache-2.0
|
|
|
|
import select
|
|
import socket
|
|
import time
|
|
|
|
from pyrad3 import new_packet as P
|
|
from pyrad3 import new_host
|
|
|
|
SUPPORTED_SEND_TYPES = [
|
|
P.AccessRequest,
|
|
P.AccountingRequest,
|
|
P.CoARequest,
|
|
]
|
|
|
|
PACKET_TYPE_PORT_MAPPING = {
|
|
P.AccessRequest: 'authport',
|
|
P.AccountingRequest: 'acctport',
|
|
P.CoARequest: 'coaport',
|
|
}
|
|
|
|
class Timeout(Exception):
|
|
pass
|
|
|
|
class UnsupportedPacketType(Exception):
|
|
pass
|
|
|
|
|
|
class Client(new_host.Host):
|
|
def __init__(self, server, secret, radius_dictionary, **kwargs):
|
|
super().__init__(secret, radius_dictionary, **kwargs)
|
|
self.server = server
|
|
self._socket = None
|
|
self._poll = None
|
|
|
|
def bind(self, addr):
|
|
self._socket_close()
|
|
self._socket_open()
|
|
self._socket.bind(addr)
|
|
|
|
def _socket_open(self):
|
|
if self._socket is not None:
|
|
return
|
|
try:
|
|
family = socket.getaddrinfo(self.server, 'www')[0][0]
|
|
except socket.gaierror:
|
|
family = socket.AF_INET
|
|
self._socket = socket.socket(family, socket.SOCK_DGRAM)
|
|
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
self._poll = select.poll()
|
|
self._poll.register(self._socket, select.POLLIN)
|
|
|
|
def _socket_close(self):
|
|
if self._socket is not None:
|
|
self._poll.unregister(self._socket)
|
|
self._socket.close()
|
|
self._socket = None
|
|
|
|
def _select_port(self, packet):
|
|
try:
|
|
port_type = PACKET_TYPE_PORT_MAPPING[packet.code]
|
|
return getattr(self, port_type)
|
|
except (AttributeError, KeyError):
|
|
pass
|
|
raise UnsupportedPacketType(f"The packet type {packet.code} by Client")
|
|
|
|
def _send_packet(self, packet):
|
|
port = self._select_port(packet)
|
|
|
|
raw_packet = packet.serialize()
|
|
|
|
for _attempt in range(self.retries):
|
|
now = time.time()
|
|
waitto = now + self.timeout
|
|
|
|
self._socket.sendto(raw_packet, (self.server, port))
|
|
|
|
while now < waitto:
|
|
if not self._poll.poll((waitto - now) * 1000):
|
|
# socket is not ready for some reason
|
|
now = time.time()
|
|
continue
|
|
|
|
rawreply = self._socket.recv(4096)
|
|
try:
|
|
return packet.verify_reply(rawreply)
|
|
except P.PacketError:
|
|
pass
|
|
|
|
# timed out: try the next attempt after increasing the acct delay time
|
|
if packet.code == packet.AccountingRequest:
|
|
packet.increase_acct_delay_time(self.timeout)
|
|
raw_packet = packet.serialize()
|
|
|
|
raise Timeout
|