bpo-37345: Add formal UDPLITE support (GH-14258)
At the moment you can definitely use UDPLITE sockets on Linux systems, but it would be good if this support were formalized such that you can detect support at runtime easily. At the moment, to make and use a UDPLITE socket requires something like the following code: ``` >>> import socket >>> a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 136) >>> b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 136) >>> a.bind(('localhost', 44444)) >>> b.sendto(b'test'*256, ('localhost', 44444)) >>> b.setsockopt(136, 10, 16) >>> b.sendto(b'test'*256, ('localhost', 44444)) >>> b.setsockopt(136, 10, 32) >>> b.sendto(b'test'*256, ('localhost', 44444)) >>> b.setsockopt(136, 10, 64) >>> b.sendto(b'test'*256, ('localhost', 44444)) ``` If you look at this through Wireshark, you can see that the packets are different in that the checksums and checksum coverages change. With the pull request that I am submitting momentarily, you could do the following code instead: ``` >>> import socket >>> a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDPLITE) >>> b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDPLITE) >>> a.bind(('localhost', 44444)) >>> b.sendto(b'test'*256, ('localhost', 44444)) >>> b.set_send_checksum_coverage(16) >>> b.sendto(b'test'*256, ('localhost', 44444)) >>> b.set_send_checksum_coverage(32) >>> b.sendto(b'test'*256, ('localhost', 44444)) >>> b.set_send_checksum_coverage(64) >>> b.sendto(b'test'*256, ('localhost', 44444)) ``` One can also detect support for UDPLITE just by checking ``` >>> hasattr(socket, 'IPPROTO_UDPLITE') ``` https://bugs.python.org/issue37345
This commit is contained in:
parent
770847a7db
commit
2ac3bab2a6
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue