Fleshing out server handler implementation.

git-svn-id: https://tftpy.svn.sourceforge.net/svnroot/tftpy/trunk@38 63283fd4-ec1e-0410-9879-cb7f675518da
This commit is contained in:
msoulier 2006-12-10 23:23:59 +00:00
parent fc2a587641
commit b5a96ec96e
2 changed files with 121 additions and 35 deletions

12
README
View file

@ -21,13 +21,19 @@ planned for 0.2.
About Tftpy:
============
Purpose:
--------
Tftpy is a TFTP library for the Python programming language. It includes
client and server classes, with sample implementations. Hooks are included for
easy inclusion in a UI for populating progress indicators. It supports RFCs
1350, 2347 and 2348.
Dependencies:
-------------
This library was developed against Python 2.3.
Trifles:
--------
Project page: http://sourceforge.net/projects/tftpy/
License is the CNRI Python License.
@ -35,6 +41,12 @@ http://www.opensource.org/licenses/pythonpl.php
See COPYING in this distribution.
Limitations:
------------
- Server is not yet implemented
- Only 'octet' mode is supported
- Uploading is not yet tested
Author:
=======

View file

@ -43,6 +43,19 @@ def setLogLevel(level):
not created."""
global logger
logger.setLevel(level)
class TftpErrors(object):
"""This class is a convenience for defining the common tftp error codes, and making
them more readable in the code."""
NotDefined = 0
FileNotFound = 1
AccessViolation = 2
DiskFull = 3
IllegalTftpOp = 4
UnknownTID = 5
FileAlreadyExists = 6
NoSuchUser = 7
FailedNegotiation = 8
class TftpException(Exception):
"""This class is the parent class of all exceptions regarding the handling
@ -336,6 +349,7 @@ ERROR | 05 | ErrorCode | ErrMsg | 0 |
5 Unknown transfer ID.
6 File already exists.
7 No such user.
8 Failed to negotiate options
"""
def __init__(self):
TftpPacket.__init__(self)
@ -487,14 +501,21 @@ class TftpSession(object):
code should be in this class."""
def __init__(self):
"Class constructor. Note that the state property must be a TftpState
object."
"""Class constructor. Note that the state property must be a TftpState
object."""
self.options = None
self.state = TftpState()
self.dups = 0
self.errors = 0
def senderror(self, sock, errorcode, address, port):
"""This method uses the socket passed, and uses the errorcode, address and port to
compose and send an error packet."""
errpkt = TftpPacketERR()
errpkt.errorcode = errorcode
self.sock.send(errpkt.encode().buffer, (address, port))
class TftpServer(object):
class TftpServer(TftpSession):
"""This class implements a tftp server object."""
def __init__(self):
@ -513,49 +534,78 @@ class TftpServer(object):
"""Start a server listening on the supplied interface and port. This
defaults to INADDR_ANY (all interfaces) and UDP port 69. You can also
supply a different socket timeout value, if desired."""
import select
tftp_factory = TftpPacketFactory()
logger.info("Server requested on ip %s, port %s" % (listenip, listenport))
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
logger.info("Starting receive loop...")
while True:
(buffer, (raddress, rport)) = self.sock.recvfrom(MAX_BLKSIZE)
recvpkt = tftp_factory.parse(buffer)
key = "%s:%s" % (raddress, rport)
# Build the inputlist array of sockets to select() on.
inputlist = []
inputlist.append(self.sock)
for key in self.handlers:
inputlist.append(self.handlers[key].sock)
# Block until some socket has input on it.
logger.debug("Performing select on this inputlist: %s" % inputlist)
readyinput, readyoutput, readyspecial = select.select(inputlist, [], [])
#(buffer, (raddress, rport)) = self.sock.recvfrom(MAX_BLKSIZE)
#recvpkt = tftp_factory.parse(buffer)
#key = "%s:%s" % (raddress, rport)
for readysock in readyinput:
if readysock == self.sock:
logger.debug("Data ready on our main socket")
buffer, (raddress, rport) = self.sock.recvfrom(MAX_BLKSIZE)
logger.debug("Read %d bytes" % len(buffer))
recvpkt = tftp_factory.parse(buffer)
key = "%s:%s" % (raddress, rport)
if isinstance(recvpkt, TftpPacketRRQ):
logger.debug("RRQ packet from %s:%s" % (raddress, rport))
if not self.handlers.has_key(key):
logger.debug("New download request, session key = %s" % key)
self.handlers[key] = TftpServerHandler(key)
self.handlers[key].handle(recvpkt)
elif isinstance(recvpkt, TftpPacketWRQ):
logger.error("Write requests not implemented at this time.")
errpkt = TftpPacketERR()
errpkt.errorcode = 4
self.sock.sendto(errpkt.encode().buffer, (raddress, rport))
continue
if not self.handlers.has_key(key):
logger.error("No existing session with key %s" % key)
errpkt = TftpPacketERR()
errpkt.errorcode = 5
self.sock.sendto(errpkt.encode().buffer, (raddress, rport))
continue
else:
self.handlers[key].handle(recvpkt)
if isinstance(recvpkt, TftpPacketRRQ):
logger.debug("RRQ packet from %s:%s" % (raddress, rport))
if not self.handlers.has_key(key):
logger.debug("New download request, session key = %s" % key)
self.handlers[key] = TftpServerHandler(key, TftpState('rrq'))
self.handlers[key].handle((recvpkt, raddress, rport))
else:
logger.warn("Received RRQ for existing session!")
self.senderror(self.sock, TftpErrors.IllegalTftpOp, raddress, rport)
continue
elif isinstance(recvpkt, TftpPacketWRQ):
logger.error("Write requests not implemented at this time.")
self.senderror(self.sock, TftpErrors.IllegalTftpOp, raddress, rport)
continue
else:
# FIXME - this will have to change if we do symmetric UDP
logger.error("Should only receive RRQ or WRQ packets on main listen port."
" Received %s" % recvpkt)
self.senderror(self.sock, TftpErrors.IllegalTftpOp, raddress, rport)
continue
else:
for key in self.handlers:
if readysock == self.handlers[key].sock:
self.handlers[key].handle()
break
else:
raise TftpException, "Can't find the owner for this traffic!"
class TftpServerHandler(TftpSession):
"""This class implements a handler for a given server session, handling
the work for one download."""
def __init__(self, key):
def __init__(self, key, state):
TftpSession.__init__(self)
self.key = key
self.sock = self.gensock()
def handle(self, pkt):
logger.debug("Handler %s requested to handle packet %s"
% (self.key, pkt))
# Note, correct state here is important as it tells the handler whether it's
# handling a download or an upload.
self.state = state
def gensock(self):
"""This method generates a new UDP socket, whose listening port must
@ -563,6 +613,28 @@ class TftpServerHandler(TftpSession):
now, let the OS do this."""
return socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def handle(self, pkttuple=None):
"""This method informs a handler instance that it has data waiting on its socket that
it must read and process."""
recvpkt = raddress = rport = None
if pkttuple:
logger.debug("Handed pkt %s for handler %s" % (pkt, self.key))
recvpkt, raddress, rport = pkt
else:
logger.debug("Data ready for handler %s" % self.key)
buffer, (raddress, rport) = self.sock.recvfrom(MAX_BLKSIZE)
logger.debug("Read %d bytes" % len(buffer))
recvpkt = tftp_factory.parse(buffer)
if isinstance(recvpkt, TftpPacketRRQ):
logger.debug("Handler %s received RRQ packet" % self.key)
logger.debug("Requested file is %s, mode is %s" % (recvpkt.filename, recvpkt.mode))
# FIXME - only octet mode is supported at this time.
if recvpkt.mode != 'octet':
self.senderror(self.sock, TftpErrors.IllegalTftpOp, raddress, rport)
raise TftpException, "Unsupported mode: %s" % recvpkt.mode
class TftpClient(TftpSession):
"""This class is an implementation of a tftp client."""
def __init__(self, host, port, options={}):
@ -728,27 +800,29 @@ class TftpClient(TftpSession):
self.state.state = 'ack'
else:
logger.error("failed to negotiate options")
errpkt = TftpPacketERR()
errpkt.errorcode = 8
sock.sendto(errpkt.encode().buffer, (self.host, self.port))
self.senderror(sock, TftpErrors.FailedNegotiation, self.host, self.port)
self.state.state = 'err'
raise TftpException, "Failed to negotiate options"
elif isinstance(recvpkt, TftpPacketACK):
# Umm, we ACK, the server doesn't.
self.state.state = 'err'
self.senderror(sock, TftpErrors.IllegalTftpOp, self.host, self.port)
tftpassert(False, "Received ACK from server while in download")
elif isinstance(recvpkt, TftpPacketERR):
self.state.state = 'err'
self.senderror(sock, TftpErrors.IllegalTftpOp, self.host, self.port)
tftpassert(False, "Received ERR from server: " + recvpkt)
elif isinstance(recvpkt, TftpPacketWRQ):
self.state.state = 'err'
self.senderror(sock, TftpErrors.IllegalTftpOp, self.host, self.port)
tftpassert(False, "Received WRQ from server: " + recvpkt)
else:
self.state.state = 'err'
self.senderror(sock, TftpErrors.IllegalTftpOp, self.host, self.port)
tftpassert(False, "Received unknown packet type from server: "
+ recvpkt)