diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index e0dbbb4c0d3..f2e794980c0 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -200,6 +200,23 @@ created. Socket addresses are represented as follows: .. versionadded:: 3.8 +- :const:`IPPROTO_UDPLITE` is a variant of UDP which allows you to specify + what portion of a packet is covered with the checksum. It adds two socket + options that you can change. + ``self.setsockopt(IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV, length)`` will + change what portion of outgoing packets are covered by the checksum and + ``self.setsockopt(IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV, length)`` will + filter out packets which cover too little of their data. In both cases + ``length`` should be in ``range(8, 2**16, 8)``. + + Such a socket should be constructed with + ``socket(AF_INET, SOCK_DGRAM, IPPROTO_UDPLITE)`` for IPv4 or + ``socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDPLITE)`` for IPv6. + + .. availability:: Linux >= 2.6.20, FreeBSD >= 10.1-RELEASE + + .. versionadded:: 3.9 + If you use a hostname in the *host* portion of IPv4/v6 socket address, the program may show a nondeterministic behavior, as Python uses the first address returned from the DNS resolution. The socket address will be resolved diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 74662cfeb32..50094de58bf 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -136,6 +136,8 @@ HAVE_SOCKET_QIPCRTR = _have_socket_qipcrtr() HAVE_SOCKET_VSOCK = _have_socket_vsock() +HAVE_SOCKET_UDPLITE = hasattr(socket, "IPPROTO_UDPLITE") + # Size in bytes of the int type SIZEOF_INT = array.array("i").itemsize @@ -160,6 +162,12 @@ class SocketUDPTest(unittest.TestCase): self.serv.close() self.serv = None +class SocketUDPLITETest(SocketUDPTest): + + def setUp(self): + self.serv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDPLITE) + self.port = support.bind_port(self.serv) + class ThreadSafeCleanupTestCase(unittest.TestCase): """Subclass of unittest.TestCase with thread-safe cleanup methods. @@ -391,6 +399,22 @@ class ThreadedUDPSocketTest(SocketUDPTest, ThreadableTest): self.cli = None ThreadableTest.clientTearDown(self) +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +class ThreadedUDPLITESocketTest(SocketUDPLITETest, ThreadableTest): + + def __init__(self, methodName='runTest'): + SocketUDPLITETest.__init__(self, methodName=methodName) + ThreadableTest.__init__(self) + + def clientSetUp(self): + self.cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDPLITE) + + def clientTearDown(self): + self.cli.close() + self.cli = None + ThreadableTest.clientTearDown(self) + class ThreadedCANSocketTest(SocketCANTest, ThreadableTest): def __init__(self, methodName='runTest'): @@ -676,6 +700,12 @@ class UDPTestBase(InetTestBase): def newSocket(self): return socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +class UDPLITETestBase(InetTestBase): + """Base class for UDPLITE-over-IPv4 tests.""" + + def newSocket(self): + return socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDPLITE) + class SCTPStreamBase(InetTestBase): """Base class for SCTP tests in one-to-one (SOCK_STREAM) mode.""" @@ -695,6 +725,12 @@ class UDP6TestBase(Inet6TestBase): def newSocket(self): return socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) +class UDPLITE6TestBase(Inet6TestBase): + """Base class for UDPLITE-over-IPv6 tests.""" + + def newSocket(self): + return socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDPLITE) + # Test-skipping decorators for use with ThreadableTest. @@ -2359,6 +2395,37 @@ class BasicUDPTest(ThreadedUDPSocketTest): def _testRecvFromNegative(self): self.cli.sendto(MSG, 0, (HOST, self.port)) + +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +class BasicUDPLITETest(ThreadedUDPLITESocketTest): + + def __init__(self, methodName='runTest'): + ThreadedUDPLITESocketTest.__init__(self, methodName=methodName) + + def testSendtoAndRecv(self): + # Testing sendto() and Recv() over UDPLITE + msg = self.serv.recv(len(MSG)) + self.assertEqual(msg, MSG) + + def _testSendtoAndRecv(self): + self.cli.sendto(MSG, 0, (HOST, self.port)) + + def testRecvFrom(self): + # Testing recvfrom() over UDPLITE + msg, addr = self.serv.recvfrom(len(MSG)) + self.assertEqual(msg, MSG) + + def _testRecvFrom(self): + self.cli.sendto(MSG, 0, (HOST, self.port)) + + def testRecvFromNegative(self): + # Negative lengths passed to recvfrom should give ValueError. + self.assertRaises(ValueError, self.serv.recvfrom, -1) + + def _testRecvFromNegative(self): + self.cli.sendto(MSG, 0, (HOST, self.port)) + # Tests for the sendmsg()/recvmsg() interface. Where possible, the # same test code is used with different families and types of socket # (e.g. stream, datagram), and tests using recvmsg() are repeated @@ -3992,6 +4059,89 @@ class RecvmsgIntoRFC3542AncillaryUDP6Test(RecvmsgIntoMixin, pass +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +class SendrecvmsgUDPLITETestBase(SendrecvmsgDgramFlagsBase, + SendrecvmsgConnectionlessBase, + ThreadedSocketTestMixin, UDPLITETestBase): + pass + +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +@requireAttrs(socket.socket, "sendmsg") +class SendmsgUDPLITETest(SendmsgConnectionlessTests, SendrecvmsgUDPLITETestBase): + pass + +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +@requireAttrs(socket.socket, "recvmsg") +class RecvmsgUDPLITETest(RecvmsgTests, SendrecvmsgUDPLITETestBase): + pass + +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +@requireAttrs(socket.socket, "recvmsg_into") +class RecvmsgIntoUDPLITETest(RecvmsgIntoTests, SendrecvmsgUDPLITETestBase): + pass + + +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +class SendrecvmsgUDPLITE6TestBase(SendrecvmsgDgramFlagsBase, + SendrecvmsgConnectionlessBase, + ThreadedSocketTestMixin, UDPLITE6TestBase): + + def checkRecvmsgAddress(self, addr1, addr2): + # Called to compare the received address with the address of + # the peer, ignoring scope ID + self.assertEqual(addr1[:-1], addr2[:-1]) + +@requireAttrs(socket.socket, "sendmsg") +@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.') +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +@requireSocket("AF_INET6", "SOCK_DGRAM") +class SendmsgUDPLITE6Test(SendmsgConnectionlessTests, SendrecvmsgUDPLITE6TestBase): + pass + +@requireAttrs(socket.socket, "recvmsg") +@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.') +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +@requireSocket("AF_INET6", "SOCK_DGRAM") +class RecvmsgUDPLITE6Test(RecvmsgTests, SendrecvmsgUDPLITE6TestBase): + pass + +@requireAttrs(socket.socket, "recvmsg_into") +@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.') +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +@requireSocket("AF_INET6", "SOCK_DGRAM") +class RecvmsgIntoUDPLITE6Test(RecvmsgIntoTests, SendrecvmsgUDPLITE6TestBase): + pass + +@requireAttrs(socket.socket, "recvmsg") +@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.') +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +@requireAttrs(socket, "IPPROTO_IPV6") +@requireSocket("AF_INET6", "SOCK_DGRAM") +class RecvmsgRFC3542AncillaryUDPLITE6Test(RFC3542AncillaryTest, + SendrecvmsgUDPLITE6TestBase): + pass + +@requireAttrs(socket.socket, "recvmsg_into") +@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.') +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +@requireAttrs(socket, "IPPROTO_IPV6") +@requireSocket("AF_INET6", "SOCK_DGRAM") +class RecvmsgIntoRFC3542AncillaryUDPLITE6Test(RecvmsgIntoMixin, + RFC3542AncillaryTest, + SendrecvmsgUDPLITE6TestBase): + pass + + class SendrecvmsgTCPTestBase(SendrecvmsgConnectedBase, ConnectedStreamTestMixin, TCPTestBase): pass @@ -4998,6 +5148,31 @@ class UDPTimeoutTest(SocketUDPTest): if not ok: self.fail("recv() returned success when we did not expect it") +@unittest.skipUnless(HAVE_SOCKET_UDPLITE, + 'UDPLITE sockets required for this test.') +class UDPLITETimeoutTest(SocketUDPLITETest): + + def testUDPLITETimeout(self): + def raise_timeout(*args, **kwargs): + self.serv.settimeout(1.0) + self.serv.recv(1024) + self.assertRaises(socket.timeout, raise_timeout, + "Error generating a timeout exception (UDPLITE)") + + def testTimeoutZero(self): + ok = False + try: + self.serv.settimeout(0.0) + foo = self.serv.recv(1024) + except socket.timeout: + self.fail("caught timeout instead of error (UDPLITE)") + except OSError: + ok = True + except: + self.fail("caught unexpected exception (UDPLITE)") + if not ok: + self.fail("recv() returned success when we did not expect it") + class TestExceptions(unittest.TestCase): def testExceptionTree(self): @@ -6230,6 +6405,14 @@ def test_main(): RecvmsgRFC3542AncillaryUDP6Test, RecvmsgIntoRFC3542AncillaryUDP6Test, RecvmsgIntoUDP6Test, + SendmsgUDPLITETest, + RecvmsgUDPLITETest, + RecvmsgIntoUDPLITETest, + SendmsgUDPLITE6Test, + RecvmsgUDPLITE6Test, + RecvmsgRFC3542AncillaryUDPLITE6Test, + RecvmsgIntoRFC3542AncillaryUDPLITE6Test, + RecvmsgIntoUDPLITE6Test, SendmsgTCPTest, RecvmsgTCPTest, RecvmsgIntoTCPTest, diff --git a/Misc/NEWS.d/next/Library/2019-06-22-08-51-44.bpo-37345.o8XABX.rst b/Misc/NEWS.d/next/Library/2019-06-22-08-51-44.bpo-37345.o8XABX.rst new file mode 100644 index 00000000000..f43fd00a635 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-06-22-08-51-44.bpo-37345.o8XABX.rst @@ -0,0 +1,6 @@ +Add formal support for UDPLITE sockets. Support was present before, but it +is now easier to detect support with ``hasattr(socket, 'IPPROTO_UDPLITE')`` +and there are constants defined for each of the values needed: +:py:obj:`socket.IPPROTO_UDPLITE`, :py:obj:`UDPLITE_SEND_CSCOV`, and +:py:obj:`UDPLITE_RECV_CSCOV`. +Patch by Gabe Appleton. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 36d13e768cd..73e64f235a6 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -7737,6 +7737,17 @@ PyInit__socket(void) #else PyModule_AddIntConstant(m, "IPPROTO_UDP", 17); #endif +#ifdef IPPROTO_UDPLITE + PyModule_AddIntMacro(m, IPPROTO_UDPLITE); + #ifndef UDPLITE_SEND_CSCOV + #define UDPLITE_SEND_CSCOV 10 + #endif + PyModule_AddIntMacro(m, UDPLITE_SEND_CSCOV); + #ifndef UDPLITE_RECV_CSCOV + #define UDPLITE_RECV_CSCOV 11 + #endif + PyModule_AddIntMacro(m, UDPLITE_RECV_CSCOV); +#endif #ifdef IPPROTO_IDP PyModule_AddIntMacro(m, IPPROTO_IDP); #endif