summaryrefslogtreecommitdiffstats
path: root/tester/rt/tftpy/TftpServer.py
diff options
context:
space:
mode:
Diffstat (limited to 'tester/rt/tftpy/TftpServer.py')
-rw-r--r--tester/rt/tftpy/TftpServer.py271
1 files changed, 0 insertions, 271 deletions
diff --git a/tester/rt/tftpy/TftpServer.py b/tester/rt/tftpy/TftpServer.py
deleted file mode 100644
index 2789322..0000000
--- a/tester/rt/tftpy/TftpServer.py
+++ /dev/null
@@ -1,271 +0,0 @@
-# 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."""
-
-
-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.
-
- tftproot is the path to the tftproot directory to serve files from and/or
- write them to.
-
- dyn_file_func is a callable that takes a requested download
- path that is not present on the file system and must return either a
- file-like object to read from or None if the path should appear as not
- found. This permits the serving of dynamic content.
-
- upload_open is a callable that is triggered on every upload with the
- requested destination path and server context. It must either return a
- file-like object ready for writing or None if the path is invalid."""
-
- def __init__(self,
- tftproot='/tftpboot',
- dyn_file_func=None,
- upload_open=None):
- self.listenip = None
- self.listenport = None
- self.sock = None
- # FIXME: What about multiple roots?
- self.root = os.path.abspath(tftproot)
- self.dyn_file_func = dyn_file_func
- self.upload_open = upload_open
- # A dict of sessions, where each session is keyed by a string like
- # ip:tid for the remote end.
- self.sessions = {}
- # A threading event to help threads synchronize with the server
- # is_running state.
- self.is_running = threading.Event()
-
- self.shutdown_gracefully = False
- self.shutdown_immediately = False
-
- for name in 'dyn_file_func', 'upload_open':
- attr = getattr(self, name)
- if attr and not callable(attr):
- 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):
- raise TftpException("The tftproot must be a directory.")
- else:
- log.debug("tftproot %s is a directory" % self.root)
- if os.access(self.root, os.R_OK):
- log.debug("tftproot %s is readable" % self.root)
- else:
- raise TftpException("The tftproot must be readable")
- if os.access(self.root, os.W_OK):
- log.debug("tftproot %s is writable" % self.root)
- else:
- log.warning("The tftproot %s is not writable" % self.root)
- else:
- raise TftpException("The tftproot does not exist.")
-
- def __del__(self):
- if self.sock is not None:
- try:
- self.sock.close()
- except:
- pass
-
- def listen(self, listenip="", listenport=DEF_TFTP_PORT,
- timeout=SOCK_TIMEOUT):
- """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."""
- tftp_factory = TftpPacketFactory()
-
- # Don't use new 2.5 ternary operator yet
- # listenip = listenip if listenip else '0.0.0.0'
- if not listenip: listenip = '0.0.0.0'
- log.info("Server requested on ip %s, port %s" % (listenip, listenport))
- try:
- # FIXME - sockets should be non-blocking
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self.sock.bind((listenip, listenport))
- _, self.listenport = self.sock.getsockname()
- except socket.error as err:
- # Reraise it for now.
- raise err
-
- self.is_running.set()
-
- log.info("Starting receive loop...")
- while True:
- log.debug("shutdown_immediately is %s" % self.shutdown_immediately)
- log.debug("shutdown_gracefully is %s" % self.shutdown_gracefully)
- if self.shutdown_immediately:
- log.warning("Shutting down now. Session count: %d" %
- len(self.sessions))
- self.sock.close()
- for key in self.sessions:
- self.sessions[key].end()
- self.sessions = []
- break
-
- elif self.shutdown_gracefully:
- if not self.sessions:
- log.warning("In graceful shutdown mode and all "
- "sessions complete.")
- self.sock.close()
- break
-
- # Build the inputlist array of sockets to select() on.
- inputlist = []
- inputlist.append(self.sock)
- for key in self.sessions:
- inputlist.append(self.sessions[key].sock)
-
- # Block until some socket has input on it.
- log.debug("Performing select on this inputlist: %s", inputlist)
- try:
- readyinput, readyoutput, readyspecial = \
- select.select(inputlist, [], [], SOCK_TIMEOUT)
- except select.error as err:
- if err[0] == EINTR:
- # Interrupted system call
- log.debug("Interrupted syscall, retrying")
- continue
- else:
- raise
-
- deletion_list = []
-
- # Handle the available data, if any. Maybe we timed-out.
- for readysock in readyinput:
- # Is the traffic on the main server socket? ie. new session?
- if readysock == self.sock:
- log.debug("Data ready on our main socket")
- buffer, (raddress, rport) = self.sock.recvfrom(MAX_BLKSIZE)
-
- log.debug("Read %d bytes", len(buffer))
-
- if self.shutdown_gracefully:
- log.warning("Discarding data on main port, "
- "in graceful shutdown mode")
- continue
-
- # Forge a session key based on the client's IP and port,
- # which should safely work through NAT.
- key = "%s:%s" % (raddress, rport)
-
- if not key in self.sessions:
- log.debug("Creating new server context for "
- "session key = %s" % key)
- try:
- self.sessions[key] = TftpContextServer(raddress,
- rport,
- timeout,
- self.root,
- self.dyn_file_func,
- self.upload_open)
- self.sessions[key].start(buffer)
- except TftpException as err:
- deletion_list.append(key)
- log.error("Fatal exception thrown from "
- "session %s: %s" % (key, str(err)))
- except KeyboardInterrupt:
- pass
- except:
- deletion_list.append(key)
- log.error("Fatal exception thrown from "
- "session %s: %s" % (key, str(err)))
- else:
- log.warning("received traffic on main socket for "
- "existing session??")
- log.info("Currently handling these sessions:")
- for session_key, session in list(self.sessions.items()):
- log.info(" %s" % session)
- else:
- # Must find the owner of this traffic.
- for key in self.sessions:
- if readysock == self.sessions[key].sock:
- log.debug("Matched input to session key %s"
- % key)
- try:
- self.sessions[key].cycle()
- if self.sessions[key].state == None:
- log.info("Successful transfer.")
- deletion_list.append(key)
- except TftpException as err:
- deletion_list.append(key)
- log.error("Fatal exception thrown from "
- "session %s: %s"
- % (key, str(err)))
- # Break out of for loop since we found the correct
- # session.
- break
- else:
- log.error("Can't find the owner for this packet. "
- "Discarding.")
-
- log.debug("Looping on all sessions to check for timeouts")
- now = time.time()
- for key in self.sessions:
- try:
- self.sessions[key].checkTimeout(now)
- except TftpTimeout as err:
- log.error(str(err))
- self.sessions[key].retry_count += 1
- if self.sessions[key].retry_count >= TIMEOUT_RETRIES:
- log.debug("hit max retries on %s, giving up" %
- self.sessions[key])
- deletion_list.append(key)
- else:
- log.debug("resending on session %s" % self.sessions[key])
- self.sessions[key].state.resendLast()
-
- log.debug("Iterating deletion list.")
- for key in deletion_list:
- log.info('')
- log.info("Session %s complete" % key)
- if key in self.sessions:
- log.debug("Gathering up metrics from session before deleting")
- self.sessions[key].end()
- metrics = self.sessions[key].metrics
- if metrics.duration == 0:
- log.info("Duration too short, rate undetermined")
- else:
- log.info("Transferred %d bytes in %.2f seconds"
- % (metrics.bytes, metrics.duration))
- log.info("Average rate: %.2f kbps" % metrics.kbps)
- log.info("%.2f bytes in resent data" % metrics.resent_bytes)
- log.info("%d duplicate packets" % metrics.dupcount)
- log.debug("Deleting session %s" % key)
- del self.sessions[key]
- log.debug("Session list is now %s" % self.sessions)
- else:
- log.warning(
- "Strange, session %s is not on the deletion list" % key)
-
- self.is_running.clear()
-
- log.debug("server returning from while loop")
- self.shutdown_gracefully = self.shutdown_immediately = False
-
- def stop(self, now=False):
- """Stop the server gracefully. Do not take any new transfers,
- but complete the existing ones. If force is True, drop everything
- and stop. Note, immediately will not interrupt the select loop, it
- will happen when the server returns on ready data, or a timeout.
- ie. SOCK_TIMEOUT"""
- if now:
- self.shutdown_immediately = True
- else:
- self.shutdown_gracefully = True