From f24e11688eb246503034cf41658ea26f511e25be Mon Sep 17 00:00:00 2001 From: Chris Johns Date: Wed, 7 Nov 2018 14:58:17 +1100 Subject: tester: Update the Python TFTP server to fix Python3 issues. Updated to af2f2fe89a3bf45748b78703820efb0986a8207a. Repo is https://github.com/msoulier/tftpy.git --- tester/rt/tftp.py | 3 +- tester/rt/tftpy/TftpClient.py | 9 +- tester/rt/tftpy/TftpContexts.py | 47 ++++++-- tester/rt/tftpy/TftpPacketFactory.py | 9 +- tester/rt/tftpy/TftpPacketTypes.py | 217 ++++++++++++++++++++--------------- tester/rt/tftpy/TftpServer.py | 24 ++-- tester/rt/tftpy/TftpShared.py | 52 ++------- tester/rt/tftpy/TftpStates.py | 53 +++++---- tester/rt/tftpy/__init__.py | 23 ++-- 9 files changed, 243 insertions(+), 194 deletions(-) (limited to 'tester') diff --git a/tester/rt/tftp.py b/tester/rt/tftp.py index 52deabc..c91ae51 100644 --- a/tester/rt/tftp.py +++ b/tester/rt/tftp.py @@ -119,7 +119,8 @@ class tftp(object): return None def _listener(self): - tftpy.log.setLevel(100) + tftpy_log = logging.getLogger('tftpy') + tftpy_log.setLevel(100) try: self.server = tftpy.TftpServer(tftproot = '.', dyn_file_func = self._exe_handle) diff --git a/tester/rt/tftpy/TftpClient.py b/tester/rt/tftpy/TftpClient.py index 2763dda..eb82c05 100644 --- a/tester/rt/tftpy/TftpClient.py +++ b/tester/rt/tftpy/TftpClient.py @@ -1,13 +1,18 @@ +# vim: ts=4 sw=4 et ai: +# -*- coding: utf8 -*- """This module implements the TFTP Client functionality. Instantiate an instance of the client, and then use its upload or download method. Logging is performed via a standard logging object set in TftpShared.""" -from __future__ import absolute_import, division, print_function, unicode_literals + import types +import logging from .TftpShared import * from .TftpPacketTypes import * from .TftpContexts import TftpContextClientDownload, TftpContextClientUpload +log = logging.getLogger('tftpy.TftpClient') + class TftpClient(TftpSession): """This class is an implementation of a tftp client. Once instantiated, a download can be initiated via the download() method, or an upload via the @@ -23,7 +28,7 @@ class TftpClient(TftpSession): self.localip = localip if 'blksize' in self.options: size = self.options['blksize'] - tftpassert(types.IntType == type(size), "blksize must be an int") + tftpassert(int == type(size), "blksize must be an int") if size < MIN_BLKSIZE or size > MAX_BLKSIZE: raise TftpException("Invalid blksize: %d" % size) diff --git a/tester/rt/tftpy/TftpContexts.py b/tester/rt/tftpy/TftpContexts.py index 271441b..da85886 100644 --- a/tester/rt/tftpy/TftpContexts.py +++ b/tester/rt/tftpy/TftpContexts.py @@ -1,3 +1,5 @@ +# vim: ts=4 sw=4 et ai: +# -*- coding: utf8 -*- """This module implements all contexts for state handling during uploads and downloads, the main interface to which being the TftpContext base class. @@ -8,12 +10,18 @@ the next packet in the transfer, and returns a state object until the transfer is complete, at which point it returns None. That is, unless there is a fatal error, in which case a TftpException is returned instead.""" -from __future__ import absolute_import, division, print_function, unicode_literals + from .TftpShared import * from .TftpPacketTypes import * from .TftpPacketFactory import TftpPacketFactory from .TftpStates import * -import socket, time, sys +import socket +import time +import sys +import os +import logging + +log = logging.getLogger('tftpy.TftpContext') ############################################################################### # Utility classes @@ -120,13 +128,14 @@ class TftpContext(object): def start(self): raise NotImplementedError("Abstract method") - def end(self): + def end(self, close_fileobj=True): """Perform session cleanup, since the end method should always be called explicitely by the calling code, this works better than the - destructor.""" - log.debug("in TftpContext.end") + destructor. + Set close_fileobj to False so fileobj can be returned open.""" + log.debug("in TftpContext.end - closing socket") self.sock.close() - if self.fileobj is not None and not self.fileobj.closed: + if close_fileobj and self.fileobj is not None and not self.fileobj.closed: log.debug("self.fileobj is open - closing") self.fileobj.close() @@ -159,7 +168,7 @@ class TftpContext(object): try: (buffer, (raddress, rport)) = self.sock.recvfrom(MAX_BLKSIZE) except socket.timeout: - log.warn("Timeout waiting for traffic, retrying...") + log.warning("Timeout waiting for traffic, retrying...") raise TftpTimeout("Timed-out waiting for traffic") # Ok, we've received a packet. Log it. @@ -173,11 +182,11 @@ class TftpContext(object): # Check for known "connection". if raddress != self.address: - log.warn("Received traffic from %s, expected host %s. Discarding" + log.warning("Received traffic from %s, expected host %s. Discarding" % (raddress, self.host)) if self.tidport and self.tidport != rport: - log.warn("Received traffic from %s:%s but we're " + log.warning("Received traffic from %s:%s but we're " "connected to %s:%s. Discarding." % (raddress, rport, self.host, self.tidport)) @@ -315,7 +324,7 @@ class TftpContextClientUpload(TftpContext): log.debug("hit max retries, giving up") raise else: - log.warn("resending last packet") + log.warning("resending last packet") self.state.resendLast() def end(self): @@ -347,13 +356,16 @@ class TftpContextClientDownload(TftpContext): self.file_to_transfer = filename self.options = options self.packethook = packethook + self.filelike_fileobj = False # If the output object has a write() function, # assume it is file-like. if hasattr(output, 'write'): self.fileobj = output + self.filelike_fileobj = True # If the output filename is -, then use stdout elif output == '-': self.fileobj = sys.stdout + self.filelike_fileobj = True else: self.fileobj = open(output, "wb") @@ -395,12 +407,23 @@ class TftpContextClientDownload(TftpContext): log.debug("hit max retries, giving up") raise else: - log.warn("resending last packet") + log.warning("resending last packet") self.state.resendLast() + except TftpFileNotFoundError as err: + # If we received file not found, then we should not save the open + # output file or we'll be left with a size zero file. Delete it, + # if it exists. + log.error("Received File not found error") + if self.fileobj is not None and not self.filelike_fileobj: + if os.path.exists(self.fileobj.name): + log.debug("unlinking output file of %s", self.fileobj.name) + os.unlink(self.fileobj.name) + + raise def end(self): """Finish up the context.""" - TftpContext.end(self) + TftpContext.end(self, not self.filelike_fileobj) self.metrics.end_time = time.time() log.debug("Set metrics.end_time to %s" % self.metrics.end_time) self.metrics.compute() diff --git a/tester/rt/tftpy/TftpPacketFactory.py b/tester/rt/tftpy/TftpPacketFactory.py index 928fe07..41f39a9 100644 --- a/tester/rt/tftpy/TftpPacketFactory.py +++ b/tester/rt/tftpy/TftpPacketFactory.py @@ -1,10 +1,15 @@ +# vim: ts=4 sw=4 et ai: +# -*- coding: utf8 -*- """This module implements the TftpPacketFactory class, which can take a binary buffer, and return the appropriate TftpPacket object to represent it, via the parse() method.""" -from __future__ import absolute_import, division, print_function, unicode_literals + from .TftpShared import * from .TftpPacketTypes import * +import logging + +log = logging.getLogger('tftpy.TftpPacketFactory') class TftpPacketFactory(object): """This class generates TftpPacket objects. It is responsible for parsing @@ -25,7 +30,7 @@ class TftpPacketFactory(object): corresponding TftpPacket object. The buffer is the raw bytes off of the network.""" log.debug("parsing a %d byte packet" % len(buffer)) - (opcode,) = struct.unpack("!H", buffer[:2]) + (opcode,) = struct.unpack(str("!H"), buffer[:2]) log.debug("opcode is %d" % opcode) packet = self.__create(opcode) packet.buffer = buffer diff --git a/tester/rt/tftpy/TftpPacketTypes.py b/tester/rt/tftpy/TftpPacketTypes.py index e45bb02..3d3bdf8 100644 --- a/tester/rt/tftpy/TftpPacketTypes.py +++ b/tester/rt/tftpy/TftpPacketTypes.py @@ -1,11 +1,16 @@ +# vim: ts=4 sw=4 et ai: +# -*- coding: utf8 -*- """This module implements the packet types of TFTP itself, and the corresponding encode and decode methods for them.""" -from __future__ import absolute_import, division, print_function, unicode_literals + import struct import sys +import logging from .TftpShared import * +log = logging.getLogger('tftpy.TftpPacketTypes') + class TftpSession(object): """This class is the base class for the tftp client and server. Any shared code should be in this class.""" @@ -20,17 +25,23 @@ class TftpPacketWithOptions(object): def __init__(self): self.options = {} + # Always use unicode strings, except at the encode/decode barrier. + # Simpler to keep things clear. def setoptions(self, options): log.debug("in TftpPacketWithOptions.setoptions") - log.debug("options: %s" % options) + log.debug("options: %s", options) myoptions = {} for key in options: - newkey = str(key) - myoptions[newkey] = str(options[key]) - log.debug("populated myoptions with %s = %s" - % (newkey, myoptions[newkey])) - - log.debug("setting options hash to: %s" % myoptions) + newkey = key + if isinstance(key, bytes): + newkey = newkey.decode('ascii') + newval = options[key] + if isinstance(newval, bytes): + newval = newval.decode('ascii') + myoptions[newkey] = newval + log.debug("populated myoptions with %s = %s", newkey, myoptions[newkey]) + + log.debug("setting options hash to: %s", myoptions) self._options = myoptions def getoptions(self): @@ -46,11 +57,11 @@ class TftpPacketWithOptions(object): """This method decodes the section of the buffer that contains an unknown number of options. It returns a dictionary of option names and values.""" - format = "!" + fmt = b"!" options = {} - log.debug("decode_options: buffer is: %s" % repr(buffer)) - log.debug("size of buffer is %d bytes" % len(buffer)) + log.debug("decode_options: buffer is: %s", repr(buffer)) + log.debug("size of buffer is %d bytes", len(buffer)) if len(buffer) == 0: log.debug("size of buffer is zero, returning empty hash") return {} @@ -58,25 +69,28 @@ class TftpPacketWithOptions(object): # Count the nulls in the buffer. Each one terminates a string. log.debug("about to iterate options buffer counting nulls") length = 0 - for c in buffer: - if ord(c) == 0: - log.debug("found a null at length %d" % length) + for i in range(len(buffer)): + if ord(buffer[i:i+1]) == 0: + log.debug("found a null at length %d", length) if length > 0: - format += "%dsx" % length + fmt += b"%dsx" % length length = -1 else: raise TftpException("Invalid options in buffer") length += 1 - log.debug("about to unpack, format is: %s" % format) - mystruct = struct.unpack(format, buffer) + log.debug("about to unpack, fmt is: %s", fmt) + mystruct = struct.unpack(fmt, buffer) tftpassert(len(mystruct) % 2 == 0, "packet with odd number of option/value pairs") for i in range(0, len(mystruct), 2): - log.debug("setting option %s to %s" % (mystruct[i], mystruct[i+1])) - options[mystruct[i]] = mystruct[i+1] + key = mystruct[i].decode('ascii') + val = mystruct[i+1].decode('ascii') + log.debug("setting option %s to %s", key, val) + log.debug("types are %s and %s", type(key), type(val)) + options[key] = val return options @@ -120,46 +134,59 @@ class TftpPacketInitial(TftpPacket, TftpPacketWithOptions): tftpassert(self.filename, "filename required in initial packet") tftpassert(self.mode, "mode required in initial packet") # Make sure filename and mode are bytestrings. - self.filename = self.filename.encode('ascii') - self.mode = self.mode.encode('ascii') + filename = self.filename + mode = self.mode + if not isinstance(filename, bytes): + filename = filename.encode('ascii') + if not isinstance(self.mode, bytes): + mode = mode.encode('ascii') ptype = None if self.opcode == 1: ptype = "RRQ" else: ptype = "WRQ" - log.debug("Encoding %s packet, filename = %s, mode = %s" - % (ptype, self.filename, self.mode)) + log.debug("Encoding %s packet, filename = %s, mode = %s", + ptype, filename, mode) for key in self.options: - log.debug(" Option %s = %s" % (key, self.options[key])) + log.debug(" Option %s = %s", key, self.options[key]) - format = b"!H" - format += b"%dsx" % len(self.filename) - if self.mode == b"octet": - format += b"5sx" + fmt = b"!H" + fmt += b"%dsx" % len(filename) + if mode == b"octet": + fmt += b"5sx" else: - raise AssertionError("Unsupported mode: %s" % self.mode) - # Add options. + raise AssertionError("Unsupported mode: %s" % mode) + # Add options. Note that the options list must be bytes. options_list = [] - if len(self.options.keys()) > 0: + if len(list(self.options.keys())) > 0: log.debug("there are options to encode") for key in self.options: # Populate the option name - format += b"%dsx" % len(key) - options_list.append(key.encode('ascii')) + name = key + if not isinstance(name, bytes): + name = name.encode('ascii') + options_list.append(name) + fmt += b"%dsx" % len(name) # Populate the option value - format += b"%dsx" % len(self.options[key].encode('ascii')) - options_list.append(self.options[key].encode('ascii')) - - log.debug("format is %s" % format) - log.debug("options_list is %s" % options_list) - log.debug("size of struct is %d" % struct.calcsize(format)) - - self.buffer = struct.pack(format, + value = self.options[key] + # Work with all strings. + if isinstance(value, int): + value = str(value) + if not isinstance(value, bytes): + value = value.encode('ascii') + options_list.append(value) + fmt += b"%dsx" % len(value) + + log.debug("fmt is %s", fmt) + log.debug("options_list is %s", options_list) + log.debug("size of struct is %d", struct.calcsize(fmt)) + + self.buffer = struct.pack(fmt, self.opcode, - self.filename, - self.mode, + filename, + mode, *options_list) - log.debug("buffer is %s" % repr(self.buffer)) + log.debug("buffer is %s", repr(self.buffer)) return self def decode(self): @@ -167,18 +194,15 @@ class TftpPacketInitial(TftpPacket, TftpPacketWithOptions): # FIXME - this shares a lot of code with decode_options nulls = 0 - format = "" + fmt = b"" nulls = length = tlength = 0 log.debug("in decode: about to iterate buffer counting nulls") subbuf = self.buffer[2:] - for c in subbuf: - if sys.version_info[0] <= 2: - c = ord(c) - if c == 0: + for i in range(len(subbuf)): + if ord(subbuf[i:i+1]) == 0: nulls += 1 - log.debug("found a null at length %d, now have %d" - % (length, nulls)) - format += "%dsx" % length + log.debug("found a null at length %d, now have %d", length, nulls) + fmt += b"%dsx" % length length = -1 # At 2 nulls, we want to mark that position for decoding. if nulls == 2: @@ -186,21 +210,22 @@ class TftpPacketInitial(TftpPacket, TftpPacketWithOptions): length += 1 tlength += 1 - log.debug("hopefully found end of mode at length %d" % tlength) + log.debug("hopefully found end of mode at length %d", tlength) # length should now be the end of the mode. tftpassert(nulls == 2, "malformed packet") shortbuf = subbuf[:tlength+1] - log.debug("about to unpack buffer with format: %s" % format) - log.debug("unpacking buffer: " + repr(shortbuf)) - mystruct = struct.unpack(format, shortbuf) + log.debug("about to unpack buffer with fmt: %s", fmt) + log.debug("unpacking buffer: %s", repr(shortbuf)) + mystruct = struct.unpack(fmt, shortbuf) tftpassert(len(mystruct) == 2, "malformed packet") - self.filename = mystruct[0] - self.mode = mystruct[1].lower() # force lc - bug 17 - log.debug("set filename to %s" % self.filename) - log.debug("set mode to %s" % self.mode) + self.filename = mystruct[0].decode('ascii') + self.mode = mystruct[1].decode('ascii').lower() # force lc - bug 17 + log.debug("set filename to %s", self.filename) + log.debug("set mode to %s", self.mode) self.options = self.decode_options(subbuf[tlength+1:]) + log.debug("options dict is now %s", self.options) return self class TftpPacketRRQ(TftpPacketInitial): @@ -269,11 +294,14 @@ class TftpPacketDAT(TftpPacket): returns self for easy method chaining.""" if len(self.data) == 0: log.debug("Encoding an empty DAT packet") - format = "!HH%ds" % len(self.data) - self.buffer = struct.pack(format, + data = self.data + if not isinstance(self.data, bytes): + data = self.data.encode('ascii') + fmt = b"!HH%ds" % len(data) + self.buffer = struct.pack(fmt, self.opcode, self.blocknumber, - self.data) + data) return self def decode(self): @@ -281,14 +309,12 @@ class TftpPacketDAT(TftpPacket): easy method chaining.""" # We know the first 2 bytes are the opcode. The second two are the # block number. - (self.blocknumber,) = struct.unpack("!H", self.buffer[2:4]) - log.debug("decoding DAT packet, block number %d" % self.blocknumber) - log.debug("should be %d bytes in the packet total" - % len(self.buffer)) + (self.blocknumber,) = struct.unpack(str("!H"), self.buffer[2:4]) + log.debug("decoding DAT packet, block number %d", self.blocknumber) + log.debug("should be %d bytes in the packet total", len(self.buffer)) # Everything else is data. self.data = self.buffer[4:] - log.debug("found %d bytes of data" - % len(self.data)) + log.debug("found %d bytes of data", len(self.data)) return self class TftpPacketACK(TftpPacket): @@ -309,9 +335,9 @@ class TftpPacketACK(TftpPacket): return 'ACK packet: block %d' % self.blocknumber def encode(self): - log.debug("encoding ACK: opcode = %d, block = %d" - % (self.opcode, self.blocknumber)) - self.buffer = struct.pack("!HH", self.opcode, self.blocknumber) + log.debug("encoding ACK: opcode = %d, block = %d", + self.opcode, self.blocknumber) + self.buffer = struct.pack(str("!HH"), self.opcode, self.blocknumber) return self def decode(self): @@ -319,9 +345,9 @@ class TftpPacketACK(TftpPacket): log.debug("detected TFTP ACK but request is too large, will truncate") log.debug("buffer was: %s", repr(self.buffer)) self.buffer = self.buffer[0:4] - self.opcode, self.blocknumber = struct.unpack("!HH", self.buffer) - log.debug("decoded ACK packet: opcode = %d, block = %d" - % (self.opcode, self.blocknumber)) + self.opcode, self.blocknumber = struct.unpack(str("!HH"), self.buffer) + log.debug("decoded ACK packet: opcode = %d, block = %d", + self.opcode, self.blocknumber) return self class TftpPacketERR(TftpPacket): @@ -373,9 +399,9 @@ class TftpPacketERR(TftpPacket): def encode(self): """Encode the DAT packet based on instance variables, populating self.buffer, returning self.""" - format = "!HH%dsx" % len(self.errmsgs[self.errorcode]) - log.debug("encoding ERR packet with format %s" % format) - self.buffer = struct.pack(format, + fmt = b"!HH%dsx" % len(self.errmsgs[self.errorcode]) + log.debug("encoding ERR packet with fmt %s", fmt) + self.buffer = struct.pack(fmt, self.opcode, self.errorcode, self.errmsgs[self.errorcode]) @@ -385,18 +411,18 @@ class TftpPacketERR(TftpPacket): "Decode self.buffer, populating instance variables and return self." buflen = len(self.buffer) tftpassert(buflen >= 4, "malformed ERR packet, too short") - log.debug("Decoding ERR packet, length %s bytes" % buflen) + log.debug("Decoding ERR packet, length %s bytes", buflen) if buflen == 4: log.debug("Allowing this affront to the RFC of a 4-byte packet") - format = "!HH" - log.debug("Decoding ERR packet with format: %s" % format) - self.opcode, self.errorcode = struct.unpack(format, + fmt = b"!HH" + log.debug("Decoding ERR packet with fmt: %s", fmt) + self.opcode, self.errorcode = struct.unpack(fmt, self.buffer) else: log.debug("Good ERR packet > 4 bytes") - format = "!HH%dsx" % (len(self.buffer) - 5) - log.debug("Decoding ERR packet with format: %s" % format) - self.opcode, self.errorcode, self.errmsg = struct.unpack(format, + fmt = b"!HH%dsx" % (len(self.buffer) - 5) + log.debug("Decoding ERR packet with fmt: %s", fmt) + self.opcode, self.errorcode, self.errmsg = struct.unpack(fmt, self.buffer) log.error("ERR packet - errorcode: %d, message: %s" % (self.errorcode, self.errmsg)) @@ -419,17 +445,24 @@ class TftpPacketOACK(TftpPacket, TftpPacketWithOptions): return 'OACK packet:\n options = %s' % self.options def encode(self): - format = "!H" # opcode + fmt = b"!H" # opcode options_list = [] log.debug("in TftpPacketOACK.encode") for key in self.options: - log.debug("looping on option key %s" % key) - log.debug("value is %s" % self.options[key]) - format += "%dsx" % len(key) - format += "%dsx" % len(self.options[key]) + value = self.options[key] + if isinstance(value, int): + value = str(value) + if not isinstance(key, bytes): + key = key.encode('ascii') + if not isinstance(value, bytes): + value = value.encode('ascii') + log.debug("looping on option key %s", key) + log.debug("value is %s", value) + fmt += b"%dsx" % len(key) + fmt += b"%dsx" % len(value) options_list.append(key) - options_list.append(self.options[key]) - self.buffer = struct.pack(format, self.opcode, *options_list) + options_list.append(value) + self.buffer = struct.pack(fmt, self.opcode, *options_list) return self def decode(self): diff --git a/tester/rt/tftpy/TftpServer.py b/tester/rt/tftpy/TftpServer.py index 07c2107..95ca70e 100644 --- a/tester/rt/tftpy/TftpServer.py +++ b/tester/rt/tftpy/TftpServer.py @@ -1,18 +1,23 @@ +# vim: ts=4 sw=4 et ai: +# -*- coding: utf8 -*- """This module implements the TFTP Server functionality. Instantiate an instance of the server, and then run the listen() method to listen for client requests. Logging is performed via a standard logging object set in TftpShared.""" -from __future__ import absolute_import, division, print_function, unicode_literals + import socket, os, time import select import threading +import logging from errno import EINTR from .TftpShared import * from .TftpPacketTypes import * from .TftpPacketFactory import TftpPacketFactory from .TftpContexts import TftpContextServer +log = logging.getLogger('tftpy.TftpServer') + class TftpServer(TftpSession): """This class implements a tftp server object. Run the listen() method to listen for client requests. @@ -53,8 +58,7 @@ class TftpServer(TftpSession): for name in 'dyn_file_func', 'upload_open': attr = getattr(self, name) if attr and not callable(attr): - raise TftpException, "%s supplied, but it is not callable." % ( - name,) + raise TftpException("{} supplied, but it is not callable.".format(name)) if os.path.exists(self.root): log.debug("tftproot %s does exist", self.root) if not os.path.isdir(self.root): @@ -99,7 +103,7 @@ class TftpServer(TftpSession): log.debug("shutdown_immediately is %s" % self.shutdown_immediately) log.debug("shutdown_gracefully is %s" % self.shutdown_gracefully) if self.shutdown_immediately: - log.warn("Shutting down now. Session count: %d" % + log.warning("Shutting down now. Session count: %d" % len(self.sessions)) self.sock.close() for key in self.sessions: @@ -109,7 +113,7 @@ class TftpServer(TftpSession): elif self.shutdown_gracefully: if not self.sessions: - log.warn("In graceful shutdown mode and all " + log.warning("In graceful shutdown mode and all " "sessions complete.") self.sock.close() break @@ -124,7 +128,7 @@ class TftpServer(TftpSession): log.debug("Performing select on this inputlist: %s", inputlist) try: readyinput, readyoutput, readyspecial = \ - select.select(inputlist, [], [], timeout) + select.select(inputlist, [], [], SOCK_TIMEOUT) except select.error as err: if err[0] == EINTR: # Interrupted system call @@ -145,7 +149,7 @@ class TftpServer(TftpSession): log.debug("Read %d bytes", len(buffer)) if self.shutdown_gracefully: - log.warn("Discarding data on main port, " + log.warning("Discarding data on main port, " "in graceful shutdown mode") continue @@ -169,10 +173,10 @@ class TftpServer(TftpSession): log.error("Fatal exception thrown from " "session %s: %s" % (key, str(err))) else: - log.warn("received traffic on main socket for " + log.warning("received traffic on main socket for " "existing session??") log.info("Currently handling these sessions:") - for session_key, session in self.sessions.items(): + for session_key, session in list(self.sessions.items()): log.info(" %s" % session) else: @@ -234,7 +238,7 @@ class TftpServer(TftpSession): del self.sessions[key] log.debug("Session list is now %s" % self.sessions) else: - log.warn( + log.warning( "Strange, session %s is not on the deletion list" % key) self.is_running.clear() diff --git a/tester/rt/tftpy/TftpShared.py b/tester/rt/tftpy/TftpShared.py index 6252ebd..88530c3 100644 --- a/tester/rt/tftpy/TftpShared.py +++ b/tester/rt/tftpy/TftpShared.py @@ -1,10 +1,9 @@ +# vim: ts=4 sw=4 et ai: +# -*- coding: utf8 -*- """This module holds all objects shared by all other modules in tftpy.""" -from __future__ import absolute_import, division, print_function, unicode_literals -import logging -from logging.handlers import RotatingFileHandler -LOG_LEVEL = logging.NOTSET + MIN_BLKSIZE = 8 DEF_BLKSIZE = 512 MAX_BLKSIZE = 65536 @@ -16,40 +15,6 @@ DEF_TFTP_PORT = 69 # A hook for deliberately introducing delay in testing. DELAY_BLOCK = 0 -# Initialize the logger. -logging.basicConfig() - -# The logger used by this library. Feel free to clobber it with your own, if -# you like, as long as it conforms to Python's logging. -log = logging.getLogger('tftpy') - -def create_streamhandler(): - """add create_streamhandler output logging.DEBUG msg to stdout. - """ - console = logging.StreamHandler() - console.setLevel(logging.INFO) - formatter = logging.Formatter('%(levelname)-8s %(message)s') - console.setFormatter(formatter) - return console - -def create_rotatingfilehandler(path, maxbytes=10*1024*1024, count=20): - """ - add create_rotatingfilehandler record the logging.DEBUG msg to logfile. you can change the maxsize (10*1024*1024) - and amount of the logfiles - """ - Rthandler = RotatingFileHandler(path, maxbytes, count) - Rthandler.setLevel(logging.INFO) - formatter = logging.Formatter('%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s') - Rthandler.setFormatter(formatter) - return Rthandler - -def addHandler(hdlr): - """add handler methods - More details see the page: - https://docs.python.org/2/library/logging.handlers.html#module-logging.handlers - """ - log.addHandler(hdlr) - def tftpassert(condition, msg): """This function is a simple utility that will check the condition passed for a false state. If it finds one, it throws a TftpException @@ -58,12 +23,6 @@ def tftpassert(condition, msg): if not condition: raise TftpException(msg) -def setLogLevel(level): - """This function is a utility function for setting the internal log level. - The log level defaults to logging.NOTSET, so unwanted output to stdout is - not created.""" - log.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.""" @@ -86,3 +45,8 @@ class TftpTimeout(TftpException): """This class represents a timeout error waiting for a response from the other end.""" pass + +class TftpFileNotFoundError(TftpException): + """This class represents an error condition where we received a file + not found error.""" + pass diff --git a/tester/rt/tftpy/TftpStates.py b/tester/rt/tftpy/TftpStates.py index 801e970..42bac1d 100644 --- a/tester/rt/tftpy/TftpStates.py +++ b/tester/rt/tftpy/TftpStates.py @@ -1,3 +1,5 @@ +# vim: ts=4 sw=4 et ai: +# -*- coding: utf8 -*- """This module implements all state handling during uploads and downloads, the main interface to which being the TftpState base class. @@ -8,10 +10,13 @@ the next packet in the transfer, and returns a state object until the transfer is complete, at which point it returns None. That is, unless there is a fatal error, in which case a TftpException is returned instead.""" -from __future__ import absolute_import, division, print_function, unicode_literals + from .TftpShared import * from .TftpPacketTypes import * import os +import logging + +log = logging.getLogger('tftpy.TftpStates') ############################################################################### # State classes @@ -34,7 +39,7 @@ class TftpState(object): def handleOACK(self, pkt): """This method handles an OACK from the server, syncing any accepted options.""" - if pkt.options.keys() > 0: + if len(pkt.options.keys()) > 0: if pkt.match_options(self.context.options): log.info("Successful negotiation of options") # Set options to OACK options @@ -126,9 +131,12 @@ class TftpState(object): log.debug("In sendError, being asked to send error %d", errorcode) errpkt = TftpPacketERR() errpkt.errorcode = errorcode - self.context.sock.sendto(errpkt.encode().buffer, - (self.context.host, - self.context.tidport)) + if self.context.tidport == None: + log.debug("Error packet received outside session. Discarding") + else: + self.context.sock.sendto(errpkt.encode().buffer, + (self.context.host, + self.context.tidport)) self.context.last_pkt = errpkt def sendOACK(self): @@ -144,7 +152,7 @@ class TftpState(object): def resendLast(self): "Resend the last sent packet due to a timeout." - log.warn("Resending packet %s on sessions %s" + log.warning("Resending packet %s on sessions %s" % (self.context.last_pkt, self)) self.context.metrics.resent_bytes += len(self.context.last_pkt.buffer) self.context.metrics.add_dup(self.context.last_pkt) @@ -180,10 +188,10 @@ class TftpState(object): elif pkt.blocknumber < self.context.next_block: if pkt.blocknumber == 0: - log.warn("There is no block zero!") + log.warning("There is no block zero!") self.sendError(TftpErrors.IllegalTftpOp) raise TftpException("There is no block zero!") - log.warn("Dropping duplicate block %d" % pkt.blocknumber) + log.warning("Dropping duplicate block %d" % pkt.blocknumber) self.context.metrics.add_dup(pkt) log.debug("ACKing block %d again, just in case", pkt.blocknumber) self.sendACK(pkt.blocknumber) @@ -261,10 +269,10 @@ class TftpServerState(TftpState): # begin with a '/' strip it off as otherwise os.path.join will # treat it as absolute (regardless of whether it is ntpath or # posixpath module - if pkt.filename.startswith(self.context.root.encode()): + if pkt.filename.startswith(self.context.root): full_path = pkt.filename else: - full_path = os.path.join(self.context.root, pkt.filename.decode().lstrip('/')) + full_path = os.path.join(self.context.root, pkt.filename.lstrip('/')) # Use abspath to eliminate any remaining relative elements # (e.g. '..') and ensure that is still within the server's @@ -274,7 +282,7 @@ class TftpServerState(TftpState): if self.full_path.startswith(self.context.root): log.info("requested file is in the server root - good") else: - log.warn("requested file is not within the server root - bad") + log.warning("requested file is not within the server root - bad") self.sendError(TftpErrors.IllegalTftpOp) raise TftpException("bad file path") @@ -307,11 +315,12 @@ class TftpStateServerRecvRRQ(TftpServerState): self.sendError(TftpErrors.FileNotFound) raise TftpException("File not found: %s" % path) else: + log.warn("File not found: %s", path) self.sendError(TftpErrors.FileNotFound) - raise TftpException("File not found: %s" % path) + raise TftpException("File not found: {}".format(path)) # Options negotiation. - if sendoack and self.context.options.has_key('tsize'): + if sendoack and 'tsize' in self.context.options: # getting the file size for the tsize option. As we handle # file-like objects and not only real files, we use this seeking # method instead of asking the OS @@ -368,14 +377,14 @@ class TftpStateServerRecvWRQ(TftpServerState): f = self.context.upload_open(path, self.context) if f is None: self.sendError(TftpErrors.AccessViolation) - raise TftpException, "Dynamic path %s not permitted" % path + raise TftpException("Dynamic path %s not permitted" % path) else: self.context.fileobj = f else: log.info("Opening file %s for writing" % path) if os.path.exists(path): # FIXME: correct behavior? - log.warn("File %s exists already, overwriting..." % ( + log.warning("File %s exists already, overwriting..." % ( self.context.file_to_transfer)) # FIXME: I think we should upload to a temp file and not overwrite # the existing file until the file is successfully uploaded. @@ -443,12 +452,12 @@ class TftpStateExpectACK(TftpState): self.context.pending_complete = self.sendDAT() elif pkt.blocknumber < self.context.next_block: - log.warn("Received duplicate ACK for block %d" + log.warning("Received duplicate ACK for block %d" % pkt.blocknumber) self.context.metrics.add_dup(pkt) else: - log.warn("Oooh, time warp. Received ACK to packet we " + log.warning("Oooh, time warp. Received ACK to packet we " "didn't send yet. Discarding.") self.context.metrics.errors += 1 return self @@ -456,7 +465,7 @@ class TftpStateExpectACK(TftpState): log.error("Received ERR packet from peer: %s" % str(pkt)) raise TftpException("Received ERR packet from peer: %s" % str(pkt)) else: - log.warn("Discarding unsupported packet: %s" % str(pkt)) + log.warning("Discarding unsupported packet: %s" % str(pkt)) return self class TftpStateExpectDAT(TftpState): @@ -519,7 +528,7 @@ class TftpStateSentWRQ(TftpState): log.debug("Changing state to TftpStateExpectACK") return TftpStateExpectACK(self.context) else: - log.warn("Discarding ACK to block %s" % pkt.blocknumber) + log.warning("Discarding ACK to block %s" % pkt.blocknumber) log.debug("Still waiting for valid response from server") return self @@ -588,7 +597,11 @@ class TftpStateSentRRQ(TftpState): elif isinstance(pkt, TftpPacketERR): self.sendError(TftpErrors.IllegalTftpOp) - raise TftpException("Received ERR from server: %s" % pkt) + log.debug("Received ERR packet: %s", pkt) + if pkt.errorcode == TftpErrors.FileNotFound: + raise TftpFileNotFoundError("File not found") + else: + raise TftpException("Received ERR from server: {}".format(pkt)) else: self.sendError(TftpErrors.IllegalTftpOp) diff --git a/tester/rt/tftpy/__init__.py b/tester/rt/tftpy/__init__.py index 33f988c..71b8e3d 100644 --- a/tester/rt/tftpy/__init__.py +++ b/tester/rt/tftpy/__init__.py @@ -1,3 +1,5 @@ +# vim: ts=4 sw=4 et ai: +# -*- coding: utf8 -*- """ This library implements the tftp protocol, based on rfc 1350. http://www.faqs.org/rfcs/rfc1350.html @@ -8,19 +10,18 @@ As a client of tftpy, this is the only module that you should need to import directly. The TftpClient and TftpServer classes can be reached through it. """ -from __future__ import absolute_import, division, print_function, unicode_literals + import sys -# Make sure that this is at least Python 2.3 -required_version = (2, 3) +# Make sure that this is at least Python 2.7 +required_version = (2, 7) if sys.version_info < required_version: - raise ImportError("Requires at least Python 2.3") + raise ImportError("Requires at least Python 2.7") from .TftpShared import * -from .TftpPacketTypes import * -from .TftpPacketFactory import * -from .TftpClient import * -from .TftpServer import * -from .TftpContexts import * -from .TftpStates import * - +from . import TftpPacketTypes +from . import TftpPacketFactory +from .TftpClient import TftpClient +from .TftpServer import TftpServer +from . import TftpContexts +from . import TftpStates -- cgit v1.2.3