#!/usr/bin/env python """Some tests of closing TCP sockets. Tests what generates a FIN or RST and what behavior that causes on the peer's end. To run tests that do not require firewall modification: $ ./close_tests.py -v To run tests that do: $ sudo ./close_tests.py -v Scott Lamb """ from util import * __version__ = '$Id$' class CloseTests(TCPTests): def testDefaultLinger(self): """background close is the default""" self.assertEquals((0,0), getlinger(self.client)) def testCloseRead(self): """read(1) from a background-closed peer""" self.server = None self.assertEquals('', self.client.recv(1)) def testResetRead(self): """SO_LINGER {1,0} close() forces reset. This seems to be true even if there's no outstanding data. """ setlinger(self.server, 1, 0) self.server = None try: self.client.recv(1) except socket.error, (err, errstr): self.assertErrorEquals(errno.ECONNRESET, err) else: self.fail('should have raised socket.error') def testShutdown(self): """Shutdown dance""" self.server.shutdown(socket.SHUT_WR) self.assertEquals('', self.client.recv(1)) self.client.shutdown(socket.SHUT_WR) self.assertEquals('', self.server.recv(1)) def testCloseWrite(self): """write(1) to closed peer succeeds""" self.server = None self.assertEquals(1, self.client.send('x')) def testCloseShutdown(self): """shutdown(SHUT_WR) after peer FIN""" self.server = None self.client.shutdown(socket.SHUT_WR) err = self.client.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) # No data outstanding; no error. self.assertEquals(0, err) @firewall_test def testCloseWriteRead(self): """read(1) after peer close, local write (RFC 2525, section 2.16)""" # Close server half, but don't let a FIN get through immediately. # (This gives our RST a chance to be received first.) self.address.drop(self.client) self.server = None # This should queue a RST, since there's no way to deliver our byte # to the other socket - see RFC 2525, section 2.16. Furthermore, it # should essentially jump in front of the FIN. self.assertEquals(1, self.client.send('x')) # Let the RST through when retried. self.address.open() # We should get an error on read. try: self.client.recv(1) except socket.error, (err, errstr): self.assertErrorEquals(errno.ECONNRESET, err) else: self.fail('read() should have failed') def testWriteCloseRead(self): """read(1) after local write, peer close (RFC 2525, section 2.17)""" self.assertEquals(1, self.client.send('x')) self.server = None # We should get an error on read. try: self.client.recv(1) except socket.error, (err, errstr): self.assertErrorEquals(errno.ECONNRESET, err) else: self.fail('read() should have failed') def xtestShutdownReadWriteClose(self): """read() after peer shutdown(SHUT_RD), local write""" self.server.shutdown(socket.SHUT_RD) self.client.send('x') try: self.client.recv(1) except socket.error, (err, errstr): self.assertErrorEquals(errno.ECONNRESET, err) else: self.fail('read() should have failed') #def testCloseWriteShutdownRead(self): # """shutdown(SHUT_WR), read() after peer close, local write""" # self.server = None # self.assertEquals(1, self.client.send('x')) # self.client.shutdown(socket.SHUT_WR) def testResetShutdown(self): """shutdown(SHUT_WR) after peer reset""" setlinger(self.server, 1, 0) self.server = None try: self.client.shutdown(socket.SHUT_WR) except socket.error, (err, errstr): if err == errno.ENOTCONN: # This path taken on Linux 2.6.17. err = self.client.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) self.assertErrorEquals(errno.ECONNRESET, err) else: self.fail("Shouldn't have succeeded") def XXXtestCloseWriteRead(self): """read(1) after peer close, local write""" self.server = None self.client.send('x') try: rv = self.client.recv(1) except socket.error, (err, errstr): self.assertErrorEquals(errno.ECONNRESET, err) else: self.fail("Should have failed, instead returned '%s' (len %d)" % (rv, len(rv))) def testCloseWriteClose(self): """close() after peer close, local write""" self.server = None self.client.send('x') # A read should fail (see testCloseWriteRead) but close doesn't care. self.client = None def testResetWrite(self): """write(1) to reset peer""" setlinger(self.server, 1, 0) self.server = None try: self.client.send('x') except socket.error, (err, errstr): self.assertErrorEquals(errno.ECONNRESET, err, errstr) else: self.fail('should have raised socket.error') def testResetWriteGeterror(self): """getsockopt(SO_ERROR) after EPIPE returns true error""" setlinger(self.server, 1, 0) self.server = None try: self.client.send('x') except socket.error, (err, errstr): if err == errno.ECONNRESET: # This test isn't relevant. return self.assertErrorEquals(errno.EPIPE, err, errstr) else: self.fail('should have raised socket.error') self.assertErrorEquals(errno.ECONNRESET, self.client.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)) def testWriteClose(self): """Background close with unread data, which should send RST. Fails on Darwin 8.2.0. """ self.server.send('x') self.client = None try: self.server.recv(1) except socket.error, (err, errstr): self.assertErrorEquals(errno.ECONNRESET, err) else: self.fail("The kernel never told me that my datum was lost") @firewall_test def testLingerTimeout(self): """lingering close where no ACK is received. Darwin 8.2.0 lingers for 1/100th of the correct time. rdar://4244834. """ linger_seconds = 1 setlinger(self.server, 1, linger_seconds) self.address.drop(self.server) before = time.time() self.server = None delta = time.time() - before self.assertTrue(linger_seconds*0.5 < delta < linger_seconds*1.5, "Lingered for %f s. Should have been %f s." % (delta, linger_seconds)) @firewall_test def testNonblockingClose(self): """lingering close on a non-blocking socket. Apparently this is the only socket op that does not respect O_NONBLOCK. """ self.server.setblocking(0) self.testLingerTimeout() if __name__ == '__main__': unittest.main()