diff options
Diffstat (limited to 'tftpy/TftpClient.py')
-rw-r--r-- | tftpy/TftpClient.py | 197 |
1 files changed, 19 insertions, 178 deletions
diff --git a/tftpy/TftpClient.py b/tftpy/TftpClient.py index 4d9b5f7..da35d05 100644 --- a/tftpy/TftpClient.py +++ b/tftpy/TftpClient.py @@ -1,7 +1,7 @@ import time, types from TftpShared import * from TftpPacketFactory import * -from TftpStates import TftpContextClientDownload +from TftpStates import TftpContextClientDownload, TftpContextClientUpload class TftpClient(TftpSession): """This class is an implementation of a tftp client. Once instantiated, a @@ -63,185 +63,26 @@ class TftpClient(TftpSession): # Open the input file. # FIXME: As of the state machine, this is now broken. Need to # implement with new state machine. - self.fileobj = open(input, "rb") - recvpkt = None - curblock = 0 - start_time = time.time() - self.bytes = 0 - - tftp_factory = TftpPacketFactory() - self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.sock.settimeout(timeout) - - self.filename = filename - - self.send_wrq() - self.state.state = 'wrq' - - timeouts = 0 - while True: - try: - (buffer, (raddress, rport)) = self.sock.recvfrom(MAX_BLKSIZE) - except socket.timeout, err: - timeouts += 1 - if timeouts >= TIMEOUT_RETRIES: - raise TftpException, "Hit max timeouts, giving up." - else: - if self.state.state == 'dat' or self.state.state == 'fin': - logger.debug("Timing out on DAT. Need to resend.") - self.send_dat(packethook,resend=True) - elif self.state.state == 'wrq': - logger.debug("Timing out on WRQ.") - self.send_wrq(resend=True) - else: - tftpassert(False, - "Timing out in unsupported state %s" % - self.state.state) - continue - - recvpkt = tftp_factory.parse(buffer) - - logger.debug("Received %d bytes from %s:%s" - % (len(buffer), raddress, rport)) - - # Check for known "connection". - if raddress != self.address: - logger.warn("Received traffic from %s, expected host %s. Discarding" - % (raddress, self.host)) - continue - if self.port and self.port != rport: - logger.warn("Received traffic from %s:%s but we're " - "connected to %s:%s. Discarding." - % (raddress, rport, - self.host, self.port)) - continue - - if not self.port and self.state.state == 'wrq': - self.port = rport - logger.debug("Set remote port for session to %s" % rport) - - # Next packet type - if isinstance(recvpkt, TftpPacketACK): - logger.debug("Received an ACK from the server.") - # tftp on wrt54gl seems to answer with an ack to a wrq regardless - # if we sent options. - if recvpkt.blocknumber == 0 and self.state.state in ('oack','wrq'): - logger.debug("Received ACK with 0 blocknumber, starting upload") - self.state.state = 'dat' - self.send_dat(packethook) - else: - if self.state.state == 'dat' or self.state.state == 'fin': - if self.blocknumber == recvpkt.blocknumber: - logger.info("Received ACK for block %d" - % recvpkt.blocknumber) - if self.state.state == 'fin': - break - else: - self.send_dat(packethook) - elif recvpkt.blocknumber < self.blocknumber: - # Don't resend a DAT due to an old ACK. Fixes the - # sorceror's apprentice problem. - logger.warn("Received old ACK for block number %d" - % recvpkt.blocknumber) - else: - logger.warn("Received ACK for block number " - "%d, apparently from the future" - % recvpkt.blocknumber) - else: - logger.error("Received ACK with block number %d " - "while in state %s" - % (recvpkt.blocknumber, - self.state.state)) - - # Check other packet types. - elif isinstance(recvpkt, TftpPacketOACK): - if not self.state.state == 'wrq': - self.errors += 1 - logger.error("Received OACK in state %s" % self.state.state) - continue - - self.state.state = 'oack' - logger.info("Received OACK from server.") - if recvpkt.options.keys() > 0: - if recvpkt.match_options(self.options): - logger.info("Successful negotiation of options") - for key in self.options: - logger.info(" %s = %s" % (key, self.options[key])) - logger.debug("sending ACK to OACK") - ackpkt = TftpPacketACK() - ackpkt.blocknumber = 0 - self.sock.sendto(ackpkt.encode().buffer, (self.host, self.port)) - self.state.state = 'dat' - self.send_dat(packethook) - else: - logger.error("failed to negotiate options") - self.senderror(self.sock, TftpErrors.FailedNegotiation, self.host, self.port) - self.state.state = 'err' - raise TftpException, "Failed to negotiate options" - - elif isinstance(recvpkt, TftpPacketERR): - self.state.state = 'err' - self.senderror(self.sock, TftpErrors.IllegalTftpOp, self.host, self.port) - tftpassert(False, "Received ERR from server: " + str(recvpkt)) - - elif isinstance(recvpkt, TftpPacketWRQ): - self.state.state = 'err' - self.senderror(self.sock, TftpErrors.IllegalTftpOp, self.host, self.port) - tftpassert(False, "Received WRQ from server: " + str(recvpkt)) - - else: - self.state.state = 'err' - self.senderror(self.sock, TftpErrors.IllegalTftpOp, self.host, self.port) - tftpassert(False, "Received unknown packet type from server: " - + str(recvpkt)) - + self.context = TftpContextClientUpload(self.host, + self.iport, + filename, + input, + self.options, + packethook, + timeout) + self.context.start() + # Upload happens here + self.context.end() - # end while - self.fileobj.close() + metrics = self.context.metrics - end_time = time.time() - duration = end_time - start_time - if duration == 0: + # FIXME: Should we output this? Shouldn't we let the client control + # output? This should be in the sample client, but not in the download + # call. + if metrics.duration == 0: logger.info("Duration too short, rate undetermined") else: logger.info('') - logger.info("Uploaded %d bytes in %d seconds" % (self.bytes, duration)) - bps = (self.bytes * 8.0) / duration - kbps = bps / 1024.0 - logger.info("Average rate: %.2f kbps" % kbps) - - def send_dat(self, packethook, resend=False): - """This method reads and sends a DAT packet based on what is in self.buffer.""" - if not resend: - blksize = int(self.options['blksize']) - self.buffer = self.fileobj.read(blksize) - logger.debug("Read %d bytes into buffer" % len(self.buffer)) - if len(self.buffer) < blksize: - logger.info("Reached EOF on file %s" % self.filename) - self.state.state = 'fin' - self.blocknumber += 1 - if self.blocknumber > 65535: - logger.debug("Blocknumber rolled over to zero") - self.blocknumber = 0 - self.bytes += len(self.buffer) - else: - logger.warn("Resending block number %d" % self.blocknumber) - dat = TftpPacketDAT() - dat.data = self.buffer - dat.blocknumber = self.blocknumber - logger.debug("Sending DAT packet %d" % self.blocknumber) - self.sock.sendto(dat.encode().buffer, (self.host, self.port)) - self.timesent = time.time() - if packethook: - packethook(dat) - - def send_wrq(self, resend=False): - """This method sends a wrq""" - logger.info("Sending tftp upload request to %s" % self.host) - logger.info(" filename -> %s" % self.filename) - - wrq = TftpPacketWRQ() - wrq.filename = self.filename - wrq.mode = "octet" # FIXME - shouldn't hardcode this - wrq.options = self.options - self.sock.sendto(wrq.encode().buffer, (self.host, self.iport)) + logger.info("Downloaded %.2f bytes in %.2f seconds" % (metrics.bytes, metrics.duration)) + logger.info("Average rate: %.2f kbps" % metrics.kbps) + logger.info("Received %d duplicate packets" % metrics.dupcount)
\ No newline at end of file |