2010-07-12 01:19:01 +00:00
|
|
|
"""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."""
|
|
|
|
|
2011-07-24 03:28:45 +00:00
|
|
|
import socket, os, time
|
2009-08-16 02:36:58 +00:00
|
|
|
import select
|
2012-11-14 14:39:58 +00:00
|
|
|
import signal
|
|
|
|
import errno
|
2006-12-17 06:08:05 +00:00
|
|
|
from TftpShared import *
|
|
|
|
from TftpPacketTypes import *
|
2011-07-24 03:20:53 +00:00
|
|
|
from TftpPacketFactory import TftpPacketFactory
|
|
|
|
from TftpContexts import TftpContextServer
|
2006-12-17 06:08:05 +00:00
|
|
|
|
2012-11-14 14:39:58 +00:00
|
|
|
def until_concludes(f, *a, **kw):
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
return f(*a, **kw)
|
|
|
|
except (IOError, OSError, select.error), e:
|
|
|
|
if e.args[0] == errno.EINTR:
|
|
|
|
continue
|
|
|
|
raise
|
|
|
|
|
2006-12-17 06:08:05 +00:00
|
|
|
class TftpServer(TftpSession):
|
2010-07-12 01:19:01 +00:00
|
|
|
"""This class implements a tftp server object. Run the listen() method to
|
|
|
|
listen for client requests. It takes two optional arguments. tftproot is
|
|
|
|
the path to the tftproot directory to serve files from and/or write them
|
|
|
|
to. dyn_file_func is a callable that must return a file-like object to
|
|
|
|
read from during downloads. This permits the serving of dynamic
|
|
|
|
content."""
|
2006-12-17 06:08:05 +00:00
|
|
|
|
2009-07-21 13:40:24 +00:00
|
|
|
def __init__(self, tftproot='/tftpboot', dyn_file_func=None):
|
2006-12-17 06:08:05 +00:00
|
|
|
self.listenip = None
|
|
|
|
self.listenport = None
|
|
|
|
self.sock = None
|
2009-08-16 02:36:58 +00:00
|
|
|
# FIXME: What about multiple roots?
|
2009-04-07 21:22:37 +00:00
|
|
|
self.root = os.path.abspath(tftproot)
|
2009-08-16 02:36:58 +00:00
|
|
|
self.dyn_file_func = dyn_file_func
|
2009-08-16 23:44:57 +00:00
|
|
|
# A dict of sessions, where each session is keyed by a string like
|
2006-12-17 06:08:05 +00:00
|
|
|
# ip:tid for the remote end.
|
2009-08-16 23:44:57 +00:00
|
|
|
self.sessions = {}
|
2012-10-04 12:28:55 +00:00
|
|
|
|
2012-04-12 01:04:35 +00:00
|
|
|
if self.dyn_file_func:
|
|
|
|
if not callable(self.dyn_file_func):
|
|
|
|
raise TftpException, "A dyn_file_func supplied, but it is not callable."
|
2012-04-11 07:55:56 +00:00
|
|
|
elif os.path.exists(self.root):
|
2009-08-16 02:36:58 +00:00
|
|
|
log.debug("tftproot %s does exist" % self.root)
|
2006-12-17 06:08:05 +00:00
|
|
|
if not os.path.isdir(self.root):
|
|
|
|
raise TftpException, "The tftproot must be a directory."
|
|
|
|
else:
|
2009-08-16 02:36:58 +00:00
|
|
|
log.debug("tftproot %s is a directory" % self.root)
|
2006-12-17 06:08:05 +00:00
|
|
|
if os.access(self.root, os.R_OK):
|
2009-08-16 02:36:58 +00:00
|
|
|
log.debug("tftproot %s is readable" % self.root)
|
2006-12-17 06:08:05 +00:00
|
|
|
else:
|
|
|
|
raise TftpException, "The tftproot must be readable"
|
|
|
|
if os.access(self.root, os.W_OK):
|
2009-08-16 02:36:58 +00:00
|
|
|
log.debug("tftproot %s is writable" % self.root)
|
2006-12-17 06:08:05 +00:00
|
|
|
else:
|
2009-08-16 02:36:58 +00:00
|
|
|
log.warning("The tftproot %s is not writable" % self.root)
|
2006-12-17 06:08:05 +00:00
|
|
|
else:
|
|
|
|
raise TftpException, "The tftproot does not exist."
|
|
|
|
|
|
|
|
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()
|
2008-07-30 18:09:58 +00:00
|
|
|
|
2007-02-09 18:48:48 +00:00
|
|
|
# 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'
|
2009-08-16 02:36:58 +00:00
|
|
|
log.info("Server requested on ip %s, port %s"
|
2007-02-09 18:48:48 +00:00
|
|
|
% (listenip, listenport))
|
2006-12-17 06:08:05 +00:00
|
|
|
try:
|
2010-05-10 19:25:31 +00:00
|
|
|
# FIXME - sockets should be non-blocking
|
2006-12-17 06:08:05 +00:00
|
|
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
|
self.sock.bind((listenip, listenport))
|
|
|
|
except socket.error, err:
|
|
|
|
# Reraise it for now.
|
|
|
|
raise
|
|
|
|
|
2009-08-16 02:36:58 +00:00
|
|
|
log.info("Starting receive loop...")
|
2006-12-17 06:08:05 +00:00
|
|
|
while True:
|
|
|
|
# Build the inputlist array of sockets to select() on.
|
|
|
|
inputlist = []
|
|
|
|
inputlist.append(self.sock)
|
2009-08-16 02:36:58 +00:00
|
|
|
for key in self.sessions:
|
|
|
|
inputlist.append(self.sessions[key].sock)
|
2008-07-30 18:09:58 +00:00
|
|
|
|
2006-12-17 06:08:05 +00:00
|
|
|
# Block until some socket has input on it.
|
2009-08-16 02:36:58 +00:00
|
|
|
log.debug("Performing select on this inputlist: %s" % inputlist)
|
2012-11-14 14:39:58 +00:00
|
|
|
readyinput, readyoutput, readyspecial = until_concludes(select.select, inputlist,
|
2006-12-17 06:08:05 +00:00
|
|
|
[],
|
|
|
|
[],
|
|
|
|
SOCK_TIMEOUT)
|
2008-07-30 18:09:58 +00:00
|
|
|
|
2006-12-17 06:08:05 +00:00
|
|
|
deletion_list = []
|
2008-07-30 18:09:58 +00:00
|
|
|
|
2009-08-16 02:36:58 +00:00
|
|
|
# Handle the available data, if any. Maybe we timed-out.
|
2006-12-17 06:08:05 +00:00
|
|
|
for readysock in readyinput:
|
2009-08-16 02:36:58 +00:00
|
|
|
# Is the traffic on the main server socket? ie. new session?
|
2006-12-17 06:08:05 +00:00
|
|
|
if readysock == self.sock:
|
2009-08-16 02:36:58 +00:00
|
|
|
log.debug("Data ready on our main socket")
|
2006-12-17 06:08:05 +00:00
|
|
|
buffer, (raddress, rport) = self.sock.recvfrom(MAX_BLKSIZE)
|
|
|
|
|
2009-08-16 02:36:58 +00:00
|
|
|
log.debug("Read %d bytes" % len(buffer))
|
2006-12-17 06:08:05 +00:00
|
|
|
|
2009-08-16 23:44:57 +00:00
|
|
|
# Forge a session key based on the client's IP and port,
|
|
|
|
# which should safely work through NAT.
|
2009-08-16 02:36:58 +00:00
|
|
|
key = "%s:%s" % (raddress, rport)
|
2008-07-30 18:09:58 +00:00
|
|
|
|
2009-08-16 02:36:58 +00:00
|
|
|
if not self.sessions.has_key(key):
|
|
|
|
log.debug("Creating new server context for "
|
|
|
|
"session key = %s" % key)
|
|
|
|
self.sessions[key] = TftpContextServer(raddress,
|
|
|
|
rport,
|
|
|
|
timeout,
|
|
|
|
self.root,
|
|
|
|
self.dyn_file_func)
|
2010-10-18 11:44:02 +00:00
|
|
|
try:
|
|
|
|
self.sessions[key].start(buffer)
|
|
|
|
except TftpException, err:
|
|
|
|
deletion_list.append(key)
|
|
|
|
log.error("Fatal exception thrown from "
|
|
|
|
"session %s: %s" % (key, str(err)))
|
2006-12-17 06:08:05 +00:00
|
|
|
else:
|
2009-08-16 02:36:58 +00:00
|
|
|
log.warn("received traffic on main socket for "
|
|
|
|
"existing session??")
|
2010-05-10 19:25:31 +00:00
|
|
|
log.info("Currently handling these sessions:")
|
|
|
|
for session_key, session in self.sessions.items():
|
|
|
|
log.info(" %s" % session)
|
2008-07-30 18:09:58 +00:00
|
|
|
|
2006-12-17 06:08:05 +00:00
|
|
|
else:
|
2009-08-16 02:36:58 +00:00
|
|
|
# Must find the owner of this traffic.
|
2009-08-16 23:44:57 +00:00
|
|
|
for key in self.sessions:
|
|
|
|
if readysock == self.sessions[key].sock:
|
|
|
|
log.info("Matched input to session key %s"
|
|
|
|
% key)
|
2006-12-17 06:08:05 +00:00
|
|
|
try:
|
2009-08-16 23:44:57 +00:00
|
|
|
self.sessions[key].cycle()
|
|
|
|
if self.sessions[key].state == None:
|
2009-08-16 02:36:58 +00:00
|
|
|
log.info("Successful transfer.")
|
|
|
|
deletion_list.append(key)
|
2006-12-17 06:08:05 +00:00
|
|
|
except TftpException, err:
|
|
|
|
deletion_list.append(key)
|
2009-08-16 02:36:58 +00:00
|
|
|
log.error("Fatal exception thrown from "
|
2009-08-16 23:44:57 +00:00
|
|
|
"session %s: %s"
|
|
|
|
% (key, str(err)))
|
2010-05-10 20:11:22 +00:00
|
|
|
# Break out of for loop since we found the correct
|
|
|
|
# session.
|
2009-08-16 23:44:57 +00:00
|
|
|
break
|
2006-12-17 06:08:05 +00:00
|
|
|
|
|
|
|
else:
|
2009-08-16 02:36:58 +00:00
|
|
|
log.error("Can't find the owner for this packet. "
|
|
|
|
"Discarding.")
|
2006-12-17 06:08:05 +00:00
|
|
|
|
2009-08-16 23:44:57 +00:00
|
|
|
log.debug("Looping on all sessions to check for timeouts")
|
2006-12-17 06:08:05 +00:00
|
|
|
now = time.time()
|
2009-08-16 02:36:58 +00:00
|
|
|
for key in self.sessions:
|
2006-12-17 06:08:05 +00:00
|
|
|
try:
|
2009-08-16 02:36:58 +00:00
|
|
|
self.sessions[key].checkTimeout(now)
|
2011-07-23 23:40:53 +00:00
|
|
|
except TftpTimeout, err:
|
2009-08-16 23:44:57 +00:00
|
|
|
log.error(str(err))
|
2011-07-23 23:40:53 +00:00
|
|
|
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()
|
2006-12-17 06:08:05 +00:00
|
|
|
|
2009-08-16 02:36:58 +00:00
|
|
|
log.debug("Iterating deletion list.")
|
2006-12-17 06:08:05 +00:00
|
|
|
for key in deletion_list:
|
2009-08-19 02:27:18 +00:00
|
|
|
log.info('')
|
|
|
|
log.info("Session %s complete" % key)
|
2009-08-16 02:36:58 +00:00
|
|
|
if self.sessions.has_key(key):
|
2009-08-19 02:27:18 +00:00
|
|
|
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:
|
2010-05-10 20:11:22 +00:00
|
|
|
log.info("Transferred %d bytes in %.2f seconds"
|
2009-08-19 02:27:18 +00:00
|
|
|
% (metrics.bytes, metrics.duration))
|
|
|
|
log.info("Average rate: %.2f kbps" % metrics.kbps)
|
2009-08-19 02:38:26 +00:00
|
|
|
log.info("%.2f bytes in resent data" % metrics.resent_bytes)
|
2009-08-19 02:27:18 +00:00
|
|
|
log.info("%d duplicate packets" % metrics.dupcount)
|
2009-08-16 23:44:57 +00:00
|
|
|
log.debug("Deleting session %s" % key)
|
2009-08-16 02:36:58 +00:00
|
|
|
del self.sessions[key]
|
2010-05-10 19:25:31 +00:00
|
|
|
log.debug("Session list is now %s" % self.sessions)
|
2009-08-19 02:27:18 +00:00
|
|
|
else:
|
|
|
|
log.warn("Strange, session %s is not on the deletion list"
|
|
|
|
% key)
|