Skip to content

bpo-37345: Add formal UDPLITE support #14258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 24, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Doc/library/socket.rst
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,16 @@ created. Socket addresses are represented as follows:

.. versionadded:: 3.8

- :const:`IPPROTO_UDPLITE` is a variant on UDP which allows you to specify
what portion of a packet to cover with the checksum. It adds the methods
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rephrase.
IPPROTO_UDPLITE doesn't add the methods but allows to use UDPLITE_SEND_CSCOV / UDPLITE_RECV_CSCOV in setsockopt

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the new version make this better in your eyes?

:meth:`socket.set_send_checksum_coverage` to change what portion of
outgoing packets are covered and :meth:`socket.set_recv_checksum_coverage`
to filter out packets which cover too little of their data.

..availability:: Linux >= 2.6.20

.. versionadded:: 3.8

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
Expand Down
32 changes: 32 additions & 0 deletions Lib/socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,38 @@ def _decref_socketios(self):
if self._closed:
self.close()

if hasattr(_socket, "IPPROTO_UDPLITE"):
def set_send_checksum_coverage(self, length):
"""set_send_checksum_coverage(length) -> None

On a UDPLITE socket, this function will set the checksum coverage
for all outgoing packets. This means that any portion of the
packet after *length* bytes will not be covered by a checksum.

*length* must be greater than 8, must be a multiple of 8, and must
be smaller than 2^16."""
if self.proto != IPPROTO_UDPLITE:
raise TypeError("Socket must be UDPLITE to set this option")
elif length not in range(8, 0x10000, 8):
raise ValueError("Must be in range(8, 2**16, 8)")
self.setsockopt(IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV, length)

def set_recv_checksum_coverage(self, length):
"""set_recv_checksum_coverage(length) -> None

On a UDPLITE socket, this function will set the checksum coverage
for all incoming packets. This means that any packets received
with a checksum coverage smaller than *length* will be dropped
(and this may cause a warning in the system log).

*length* must be greater than 8, must be a multiple of 8, and must
be smaller than 2^16."""
if self.proto != IPPROTO_UDPLITE:
raise TypeError("Socket must be UDPLITE to set this option")
elif length not in range(8, 0x10000, 8):
raise ValueError("Must be in range(8, 2**16, 8)")
self.setsockopt(IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV, length)

def _real_close(self, _ss=_socket.socket):
# This function should not reference any globals. See issue #808164.
_ss.close(self)
Expand Down
183 changes: 183 additions & 0 deletions Lib/test/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ def socket_setdefaulttimeout(timeout):

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

Expand All @@ -160,6 +162,12 @@ def tearDown(self):
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.
Expand Down Expand Up @@ -391,6 +399,22 @@ def clientTearDown(self):
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'):
Expand Down Expand Up @@ -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."""

Expand All @@ -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.

Expand Down Expand Up @@ -2359,6 +2395,37 @@ def testRecvFromNegative(self):
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -4998,6 +5148,31 @@ def testTimeoutZero(self):
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):
Expand Down Expand Up @@ -6230,6 +6405,14 @@ def test_main():
RecvmsgRFC3542AncillaryUDP6Test,
RecvmsgIntoRFC3542AncillaryUDP6Test,
RecvmsgIntoUDP6Test,
SendmsgUDPLITETest,
RecvmsgUDPLITETest,
RecvmsgIntoUDPLITETest,
SendmsgUDPLITE6Test,
RecvmsgUDPLITE6Test,
RecvmsgRFC3542AncillaryUDPLITE6Test,
RecvmsgIntoRFC3542AncillaryUDPLITE6Test,
RecvmsgIntoUDPLITE6Test,
SendmsgTCPTest,
RecvmsgTCPTest,
RecvmsgIntoTCPTest,
Expand Down
11 changes: 11 additions & 0 deletions Modules/socketmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down