diff options
author | Marcin Lewandowski <marcin.lewandowski@intel.com> | 2022-03-22 13:28:14 +0100 |
---|---|---|
committer | Marcin Lewandowski <marcin.lewandowski@intel.com> | 2022-03-23 14:21:48 +0100 |
commit | a6ba72c2e6d0ce49f904556e9c1d1b765afd9e3a (patch) | |
tree | 5a9339f938aa1910dd2636d080e6733becb42106 /t | |
parent | 85af4e453e647088fda50c3108dfd00e9753af3a (diff) | |
download | tftpy-a6ba72c2e6d0ce49f904556e9c1d1b765afd9e3a.tar.gz |
Fix race condition when waiting for ACK
TFTPy is designed in a way that socket timeout is used to calculate timeout
when waiting for packet. During that time another unexpected packet may arrive.
After that the socket operation is restarted and timeout is calculated from start.
This might be a problem because both sides have timeout and these timeout
may be different or one host may be significantly faster that another.
In such situation the timeout will be never triggered as another host will
always retransmit his packet faster.
For most cases it does not matter because TFTP is always responding to packet
sent and transmission may continue. The only one exception is no response
to duplicate ACK. It is necessary to prevent Sorcerer's Apprentice Syndrome.
This patch introduces additional exception TftpTimeoutExpectACK raised
when reaching timeout during waiting on ACK of current block but receiving
duplicate ACK of previous block.
Diffstat (limited to 't')
-rw-r--r-- | t/test.py | 51 |
1 files changed, 51 insertions, 0 deletions
@@ -316,6 +316,57 @@ class TestTftpyState(unittest.TestCase): finalstate = serverstate.state.handle(ack, raddress, rport) self.assertTrue(finalstate is None) + def testServerTimeoutExpectACK(self): + raddress = "127.0.0.2" + rport = 10000 + timeout = 5 + root = os.path.dirname(os.path.abspath(__file__)) + # Testing without the dyn_func_file set. + serverstate = tftpy.TftpContexts.TftpContextServer( + raddress, rport, timeout, root + ) + + self.assertTrue(isinstance(serverstate, tftpy.TftpContexts.TftpContextServer)) + + rrq = tftpy.TftpPacketTypes.TftpPacketRRQ() + rrq.filename = "640KBFILE" + rrq.mode = "octet" + rrq.options = {} + + # Start the download. + serverstate.start(rrq.encode().buffer) + + ack = tftpy.TftpPacketTypes.TftpPacketACK() + ack.blocknumber = 1 + + # Server expects ACK at the beginning of transmission + self.assertTrue( + isinstance(serverstate.state, tftpy.TftpStates.TftpStateExpectACK) + ) + + # Receive first ACK for block 1, next block expected is 2 + serverstate.state = serverstate.state.handle(ack, raddress, rport) + self.assertTrue( + isinstance(serverstate.state, tftpy.TftpStates.TftpStateExpectACK) + ) + self.assertEqual(serverstate.state.context.next_block, 2) + + # Receive duplicate ACK for block 1, next block expected is still 2 + serverstate.state = serverstate.state.handle(ack, raddress, rport) + self.assertTrue( + isinstance(serverstate.state, tftpy.TftpStates.TftpStateExpectACK) + ) + self.assertEqual(serverstate.state.context.next_block, 2) + + # Receive duplicate ACK for block 1 after timeout for resending block 2 + serverstate.state.context.metrics.last_dat_time -= 10 # Simulate 10 seconds time warp + self.assertRaises( + tftpy.TftpTimeoutExpectACK, serverstate.state.handle, ack, raddress, rport + ) + self.assertTrue( + isinstance(serverstate.state, tftpy.TftpStates.TftpStateExpectACK) + ) + def testServerNoOptionsSubdir(self): raddress = "127.0.0.2" rport = 10000 |