pyrad
pyrad copied to clipboard
Use of 'select.poll()' in server.py (line 300) is not supported on windows
Windows does not support select.poll(), which is used on line 300 in server.py. This means that windows users cannot use the server (or in my case the fake server). This is the output:
Traceback (most recent call last): File "C:/Users/zoldham/PycharmProjects/PyRad_Test/PyRadTest.py", line 93, in <module> srv.Run() File "C:\Users\zoldham\PycharmProjects\PyRad_Test\venv\lib\site-packages\pyrad\server.py", line 300, in Run self._poll = select.poll() AttributeError: module 'select' has no attribute 'poll'
This lack of support is explicitly stated in the documentation for select.
Just curious if anyone has a work around to get this to work on Windows?
From my research this would require a rewrite of the chunk of code that select.poll() runs in and on. There seems to be no easy way to implement select.poll() on Windows.
Hi guys, I have been working on a custom version of the Server, using Threads for different sockets instead of select.poll() Probably need a few more changes, but was enough to get things moving in my case
class Server(host.Host):
"""Basic RADIUS server.
This class implements the basics of a RADIUS server. It takes care
of the details of receiving and decoding requests; processing of
the requests should be done by overloading the appropriate methods
in derived classes.
:ivar hosts: hosts who are allowed to talk to us
:type hosts: dictionary of Host class instances
:ivar _poll: poll object for network sockets
:type _poll: select.poll class instance
:ivar _fdmap: map of filedescriptors to network sockets
:type _fdmap: dictionary
:cvar MaxPacketSize: maximum size of a RADIUS packet
:type MaxPacketSize: integer
"""
MaxPacketSize = 8192
def __init__(self, addresses=[], authport=1812, acctport=1813, coaport=3799,
hosts=None, dict=None, auth_enabled=True, acct_enabled=True, coa_enabled=False):
"""Constructor.
:param addresses: IP addresses to listen on
:type addresses: sequence of strings
:param authport: port to listen on for authentication packets
:type authport: integer
:param acctport: port to listen on for accounting packets
:type acctport: integer
:param coaport: port to listen on for CoA packets
:type coaport: integer
:param hosts: hosts who we can talk to
:type hosts: dictionary mapping IP to RemoteHost class instances
:param dict: RADIUS dictionary to use
:type dict: Dictionary class instance
:param auth_enabled: enable auth server (default True)
:type auth_enabled: bool
:param acct_enabled: enable accounting server (default True)
:type acct_enabled: bool
:param coa_enabled: enable coa server (default False)
:type coa_enabled: bool
"""
host.Host.__init__(self, authport, acctport, coaport, dict)
if hosts is None:
self.hosts = {}
else:
self.hosts = hosts
self.auth_enabled = auth_enabled
self.authfds = []
self.acct_enabled = acct_enabled
self.acctfds = []
self.coa_enabled = coa_enabled
self.coafds = []
for addr in addresses:
self.BindToAddress(addr)
def _GetAddrInfo(self, addr):
"""Use getaddrinfo to lookup all addresses for each address.
Returns a list of tuples or an empty list:
[(family, address)]
:param addr: IP address to lookup
:type addr: string
"""
results = set()
try:
tmp = socket.getaddrinfo(addr, 'www')
except socket.gaierror:
return []
for el in tmp:
results.add((el[0], el[4][0]))
return results
def BindToAddress(self, addr):
"""Add an address to listen to.
An empty string indicated you want to listen on all addresses.
:param addr: IP address to listen on
:type addr: string
"""
addrFamily = self._GetAddrInfo(addr)
for (family, address) in addrFamily:
if self.auth_enabled:
authfd = socket.socket(family, socket.SOCK_DGRAM)
authfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
authfd.bind((address, self.authport))
self.authfds.append(authfd)
if self.acct_enabled:
acctfd = socket.socket(family, socket.SOCK_DGRAM)
acctfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
acctfd.bind((address, self.acctport))
self.acctfds.append(acctfd)
if self.coa_enabled:
coafd = socket.socket(family, socket.SOCK_DGRAM)
coafd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.coafds.append(coafd)
def HandleAuthPacket(self, pkt):
"""Authentication packet handler.
This is an empty function that is called when a valid
authentication packet has been received. It can be overriden in
derived classes to add custom behaviour.
:param pkt: packet to process
:type pkt: Packet class instance
"""
def HandleAcctPacket(self, pkt):
"""Accounting packet handler.
This is an empty function that is called when a valid
accounting packet has been received. It can be overriden in
derived classes to add custom behaviour.
:param pkt: packet to process
:type pkt: Packet class instance
"""
def HandleCoaPacket(self, pkt):
"""CoA packet handler.
This is an empty function that is called when a valid
accounting packet has been received. It can be overriden in
derived classes to add custom behaviour.
:param pkt: packet to process
:type pkt: Packet class instance
"""
def HandleDisconnectPacket(self, pkt):
"""CoA packet handler.
This is an empty function that is called when a valid
accounting packet has been received. It can be overriden in
derived classes to add custom behaviour.
:param pkt: packet to process
:type pkt: Packet class instance
"""
def _AddSecret(self, pkt):
"""Add secret to packets received and raise ServerPacketError
for unknown hosts.
:param pkt: packet to process
:type pkt: Packet class instance
"""
if pkt.source[0] in self.hosts:
pkt.secret = self.hosts[pkt.source[0]].secret
elif '0.0.0.0' in self.hosts:
pkt.secret = self.hosts['0.0.0.0'].secret
else:
raise server.ServerPacketError('Received packet from unknown host')
def _HandleAuthPacket(self, pkt):
"""Process a packet received on the authentication port.
If this packet should be dropped instead of processed a
ServerPacketError exception should be raised. The main loop will
drop the packet and log the reason.
:param pkt: packet to process
:type pkt: Packet class instance
"""
self._AddSecret(pkt)
if pkt.code != packet.AccessRequest:
raise server.ServerPacketError(
'Received non-authentication packet on authentication port')
self.HandleAuthPacket(pkt)
def _HandleAcctPacket(self, pkt):
"""Process a packet received on the accounting port.
If this packet should be dropped instead of processed a
ServerPacketError exception should be raised. The main loop will
drop the packet and log the reason.
:param pkt: packet to process
:type pkt: Packet class instance
"""
self._AddSecret(pkt)
if pkt.code not in [packet.AccountingRequest,
packet.AccountingResponse]:
raise server.ServerPacketError(
'Received non-accounting packet on accounting port')
self.HandleAcctPacket(pkt)
def _HandleCoaPacket(self, pkt):
"""Process a packet received on the coa port.
If this packet should be dropped instead of processed a
ServerPacketError exception should be raised. The main loop will
drop the packet and log the reason.
:param pkt: packet to process
:type pkt: Packet class instance
"""
self._AddSecret(pkt)
pkt.secret = self.hosts[pkt.source[0]].secret
if pkt.code == packet.CoARequest:
self.HandleCoaPacket(pkt)
elif pkt.code == packet.DisconnectRequest:
self.HandleDisconnectPacket(pkt)
else:
raise server.ServerPacketError('Received non-coa packet on coa port')
def _GrabPacket(self, pktgen, fd):
"""Read a packet from a network connection.
This method assumes there is data waiting for to be read.
:param fd: socket to read packet from
:type fd: socket class instance
:return: RADIUS packet
:rtype: Packet class instance
"""
(data, source) = fd.recvfrom(self.MaxPacketSize)
pkt = pktgen(data)
pkt.source = source
pkt.fd = fd
return pkt
def _PrepareSockets(self):
"""Prepare all sockets to receive packets.
"""
for fd in self.authfds + self.acctfds + self.coafds:
self._fdmap[fd.fileno()] = fd
if self.auth_enabled:
self._realauthfds = list(map(lambda x: x.fileno(), self.authfds))
if self.acct_enabled:
self._realacctfds = list(map(lambda x: x.fileno(), self.acctfds))
if self.coa_enabled:
self._realcoafds = list(map(lambda x: x.fileno(), self.coafds))
def CreateReplyPacket(self, pkt, **attributes):
"""Create a reply packet.
Create a new packet which can be returned as a reply to a received
packet.
:param pkt: original packet
:type pkt: Packet instance
"""
reply = pkt.CreateReply(**attributes)
reply.source = pkt.source
return reply
def _ProcessInput(self, fd):
"""Process available data.
If this packet should be dropped instead of processed a
PacketError exception should be raised. The main loop will
drop the packet and log the reason.
This function calls either HandleAuthPacket() or
HandleAcctPacket() depending on which socket is being
processed.
:param fd: socket to read packet from
:type fd: socket class instance
"""
if self.auth_enabled and fd.fileno() in self._realauthfds:
pkt = self._GrabPacket(lambda data, s=self: s.CreateAuthPacket(packet=data), fd)
self._HandleAuthPacket(pkt)
elif self.acct_enabled and fd.fileno() in self._realacctfds:
pkt = self._GrabPacket(lambda data, s=self: s.CreateAcctPacket(packet=data), fd)
self._HandleAcctPacket(pkt)
elif self.coa_enabled:
pkt = self._GrabPacket(lambda data, s=self: s.CreateCoAPacket(packet=data), fd)
self._HandleCoaPacket(pkt)
else:
raise server.ServerPacketError('Received packet for unknown handler')
def Run(self):
"""Main loop.
This method is the main loop for a RADIUS server. It waits
for packets to arrive via the network and calls other methods
to process them.
"""
self._fdmap = {}
self._PrepareSockets()
for fd in self._fdmap.values():
thread = threading.Thread(
name="Listener fd: {}".format(fd.fileno()),
target=self._ListenLoop, args=(fd,)
)
thread.start()
def _ListenLoop(self, fd: socket.socket):
while True:
try:
self._ProcessInput(fd)
except Exception as error:
pass
I created a simple select.poll() emulation e.g. for Windows based on select.select(). Seems to work okay.
Here's the gist.