Skip to content

Commit 2ac3bab

Browse files
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
1 parent 770847a commit 2ac3bab

File tree

4 files changed

+217
-0
lines changed

4 files changed

+217
-0
lines changed

Doc/library/socket.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,23 @@ created. Socket addresses are represented as follows:
200200

201201
.. versionadded:: 3.8
202202

203+
- :const:`IPPROTO_UDPLITE` is a variant of UDP which allows you to specify
204+
what portion of a packet is covered with the checksum. It adds two socket
205+
options that you can change.
206+
``self.setsockopt(IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV, length)`` will
207+
change what portion of outgoing packets are covered by the checksum and
208+
``self.setsockopt(IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV, length)`` will
209+
filter out packets which cover too little of their data. In both cases
210+
``length`` should be in ``range(8, 2**16, 8)``.
211+
212+
Such a socket should be constructed with
213+
``socket(AF_INET, SOCK_DGRAM, IPPROTO_UDPLITE)`` for IPv4 or
214+
``socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDPLITE)`` for IPv6.
215+
216+
.. availability:: Linux >= 2.6.20, FreeBSD >= 10.1-RELEASE
217+
218+
.. versionadded:: 3.9
219+
203220
If you use a hostname in the *host* portion of IPv4/v6 socket address, the
204221
program may show a nondeterministic behavior, as Python uses the first address
205222
returned from the DNS resolution. The socket address will be resolved

Lib/test/test_socket.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ def socket_setdefaulttimeout(timeout):
136136

137137
HAVE_SOCKET_VSOCK = _have_socket_vsock()
138138

139+
HAVE_SOCKET_UDPLITE = hasattr(socket, "IPPROTO_UDPLITE")
140+
139141
# Size in bytes of the int type
140142
SIZEOF_INT = array.array("i").itemsize
141143

@@ -160,6 +162,12 @@ def tearDown(self):
160162
self.serv.close()
161163
self.serv = None
162164

165+
class SocketUDPLITETest(SocketUDPTest):
166+
167+
def setUp(self):
168+
self.serv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDPLITE)
169+
self.port = support.bind_port(self.serv)
170+
163171
class ThreadSafeCleanupTestCase(unittest.TestCase):
164172
"""Subclass of unittest.TestCase with thread-safe cleanup methods.
165173
@@ -391,6 +399,22 @@ def clientTearDown(self):
391399
self.cli = None
392400
ThreadableTest.clientTearDown(self)
393401

402+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
403+
'UDPLITE sockets required for this test.')
404+
class ThreadedUDPLITESocketTest(SocketUDPLITETest, ThreadableTest):
405+
406+
def __init__(self, methodName='runTest'):
407+
SocketUDPLITETest.__init__(self, methodName=methodName)
408+
ThreadableTest.__init__(self)
409+
410+
def clientSetUp(self):
411+
self.cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDPLITE)
412+
413+
def clientTearDown(self):
414+
self.cli.close()
415+
self.cli = None
416+
ThreadableTest.clientTearDown(self)
417+
394418
class ThreadedCANSocketTest(SocketCANTest, ThreadableTest):
395419

396420
def __init__(self, methodName='runTest'):
@@ -676,6 +700,12 @@ class UDPTestBase(InetTestBase):
676700
def newSocket(self):
677701
return socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
678702

703+
class UDPLITETestBase(InetTestBase):
704+
"""Base class for UDPLITE-over-IPv4 tests."""
705+
706+
def newSocket(self):
707+
return socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDPLITE)
708+
679709
class SCTPStreamBase(InetTestBase):
680710
"""Base class for SCTP tests in one-to-one (SOCK_STREAM) mode."""
681711

@@ -695,6 +725,12 @@ class UDP6TestBase(Inet6TestBase):
695725
def newSocket(self):
696726
return socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
697727

728+
class UDPLITE6TestBase(Inet6TestBase):
729+
"""Base class for UDPLITE-over-IPv6 tests."""
730+
731+
def newSocket(self):
732+
return socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDPLITE)
733+
698734

699735
# Test-skipping decorators for use with ThreadableTest.
700736

@@ -2359,6 +2395,37 @@ def testRecvFromNegative(self):
23592395
def _testRecvFromNegative(self):
23602396
self.cli.sendto(MSG, 0, (HOST, self.port))
23612397

2398+
2399+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
2400+
'UDPLITE sockets required for this test.')
2401+
class BasicUDPLITETest(ThreadedUDPLITESocketTest):
2402+
2403+
def __init__(self, methodName='runTest'):
2404+
ThreadedUDPLITESocketTest.__init__(self, methodName=methodName)
2405+
2406+
def testSendtoAndRecv(self):
2407+
# Testing sendto() and Recv() over UDPLITE
2408+
msg = self.serv.recv(len(MSG))
2409+
self.assertEqual(msg, MSG)
2410+
2411+
def _testSendtoAndRecv(self):
2412+
self.cli.sendto(MSG, 0, (HOST, self.port))
2413+
2414+
def testRecvFrom(self):
2415+
# Testing recvfrom() over UDPLITE
2416+
msg, addr = self.serv.recvfrom(len(MSG))
2417+
self.assertEqual(msg, MSG)
2418+
2419+
def _testRecvFrom(self):
2420+
self.cli.sendto(MSG, 0, (HOST, self.port))
2421+
2422+
def testRecvFromNegative(self):
2423+
# Negative lengths passed to recvfrom should give ValueError.
2424+
self.assertRaises(ValueError, self.serv.recvfrom, -1)
2425+
2426+
def _testRecvFromNegative(self):
2427+
self.cli.sendto(MSG, 0, (HOST, self.port))
2428+
23622429
# Tests for the sendmsg()/recvmsg() interface. Where possible, the
23632430
# same test code is used with different families and types of socket
23642431
# (e.g. stream, datagram), and tests using recvmsg() are repeated
@@ -3992,6 +4059,89 @@ class RecvmsgIntoRFC3542AncillaryUDP6Test(RecvmsgIntoMixin,
39924059
pass
39934060

39944061

4062+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
4063+
'UDPLITE sockets required for this test.')
4064+
class SendrecvmsgUDPLITETestBase(SendrecvmsgDgramFlagsBase,
4065+
SendrecvmsgConnectionlessBase,
4066+
ThreadedSocketTestMixin, UDPLITETestBase):
4067+
pass
4068+
4069+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
4070+
'UDPLITE sockets required for this test.')
4071+
@requireAttrs(socket.socket, "sendmsg")
4072+
class SendmsgUDPLITETest(SendmsgConnectionlessTests, SendrecvmsgUDPLITETestBase):
4073+
pass
4074+
4075+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
4076+
'UDPLITE sockets required for this test.')
4077+
@requireAttrs(socket.socket, "recvmsg")
4078+
class RecvmsgUDPLITETest(RecvmsgTests, SendrecvmsgUDPLITETestBase):
4079+
pass
4080+
4081+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
4082+
'UDPLITE sockets required for this test.')
4083+
@requireAttrs(socket.socket, "recvmsg_into")
4084+
class RecvmsgIntoUDPLITETest(RecvmsgIntoTests, SendrecvmsgUDPLITETestBase):
4085+
pass
4086+
4087+
4088+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
4089+
'UDPLITE sockets required for this test.')
4090+
class SendrecvmsgUDPLITE6TestBase(SendrecvmsgDgramFlagsBase,
4091+
SendrecvmsgConnectionlessBase,
4092+
ThreadedSocketTestMixin, UDPLITE6TestBase):
4093+
4094+
def checkRecvmsgAddress(self, addr1, addr2):
4095+
# Called to compare the received address with the address of
4096+
# the peer, ignoring scope ID
4097+
self.assertEqual(addr1[:-1], addr2[:-1])
4098+
4099+
@requireAttrs(socket.socket, "sendmsg")
4100+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
4101+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
4102+
'UDPLITE sockets required for this test.')
4103+
@requireSocket("AF_INET6", "SOCK_DGRAM")
4104+
class SendmsgUDPLITE6Test(SendmsgConnectionlessTests, SendrecvmsgUDPLITE6TestBase):
4105+
pass
4106+
4107+
@requireAttrs(socket.socket, "recvmsg")
4108+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
4109+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
4110+
'UDPLITE sockets required for this test.')
4111+
@requireSocket("AF_INET6", "SOCK_DGRAM")
4112+
class RecvmsgUDPLITE6Test(RecvmsgTests, SendrecvmsgUDPLITE6TestBase):
4113+
pass
4114+
4115+
@requireAttrs(socket.socket, "recvmsg_into")
4116+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
4117+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
4118+
'UDPLITE sockets required for this test.')
4119+
@requireSocket("AF_INET6", "SOCK_DGRAM")
4120+
class RecvmsgIntoUDPLITE6Test(RecvmsgIntoTests, SendrecvmsgUDPLITE6TestBase):
4121+
pass
4122+
4123+
@requireAttrs(socket.socket, "recvmsg")
4124+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
4125+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
4126+
'UDPLITE sockets required for this test.')
4127+
@requireAttrs(socket, "IPPROTO_IPV6")
4128+
@requireSocket("AF_INET6", "SOCK_DGRAM")
4129+
class RecvmsgRFC3542AncillaryUDPLITE6Test(RFC3542AncillaryTest,
4130+
SendrecvmsgUDPLITE6TestBase):
4131+
pass
4132+
4133+
@requireAttrs(socket.socket, "recvmsg_into")
4134+
@unittest.skipUnless(support.IPV6_ENABLED, 'IPv6 required for this test.')
4135+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
4136+
'UDPLITE sockets required for this test.')
4137+
@requireAttrs(socket, "IPPROTO_IPV6")
4138+
@requireSocket("AF_INET6", "SOCK_DGRAM")
4139+
class RecvmsgIntoRFC3542AncillaryUDPLITE6Test(RecvmsgIntoMixin,
4140+
RFC3542AncillaryTest,
4141+
SendrecvmsgUDPLITE6TestBase):
4142+
pass
4143+
4144+
39954145
class SendrecvmsgTCPTestBase(SendrecvmsgConnectedBase,
39964146
ConnectedStreamTestMixin, TCPTestBase):
39974147
pass
@@ -4998,6 +5148,31 @@ def testTimeoutZero(self):
49985148
if not ok:
49995149
self.fail("recv() returned success when we did not expect it")
50005150

5151+
@unittest.skipUnless(HAVE_SOCKET_UDPLITE,
5152+
'UDPLITE sockets required for this test.')
5153+
class UDPLITETimeoutTest(SocketUDPLITETest):
5154+
5155+
def testUDPLITETimeout(self):
5156+
def raise_timeout(*args, **kwargs):
5157+
self.serv.settimeout(1.0)
5158+
self.serv.recv(1024)
5159+
self.assertRaises(socket.timeout, raise_timeout,
5160+
"Error generating a timeout exception (UDPLITE)")
5161+
5162+
def testTimeoutZero(self):
5163+
ok = False
5164+
try:
5165+
self.serv.settimeout(0.0)
5166+
foo = self.serv.recv(1024)
5167+
except socket.timeout:
5168+
self.fail("caught timeout instead of error (UDPLITE)")
5169+
except OSError:
5170+
ok = True
5171+
except:
5172+
self.fail("caught unexpected exception (UDPLITE)")
5173+
if not ok:
5174+
self.fail("recv() returned success when we did not expect it")
5175+
50015176
class TestExceptions(unittest.TestCase):
50025177

50035178
def testExceptionTree(self):
@@ -6230,6 +6405,14 @@ def test_main():
62306405
RecvmsgRFC3542AncillaryUDP6Test,
62316406
RecvmsgIntoRFC3542AncillaryUDP6Test,
62326407
RecvmsgIntoUDP6Test,
6408+
SendmsgUDPLITETest,
6409+
RecvmsgUDPLITETest,
6410+
RecvmsgIntoUDPLITETest,
6411+
SendmsgUDPLITE6Test,
6412+
RecvmsgUDPLITE6Test,
6413+
RecvmsgRFC3542AncillaryUDPLITE6Test,
6414+
RecvmsgIntoRFC3542AncillaryUDPLITE6Test,
6415+
RecvmsgIntoUDPLITE6Test,
62336416
SendmsgTCPTest,
62346417
RecvmsgTCPTest,
62356418
RecvmsgIntoTCPTest,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Add formal support for UDPLITE sockets. Support was present before, but it
2+
is now easier to detect support with ``hasattr(socket, 'IPPROTO_UDPLITE')``
3+
and there are constants defined for each of the values needed:
4+
:py:obj:`socket.IPPROTO_UDPLITE`, :py:obj:`UDPLITE_SEND_CSCOV`, and
5+
:py:obj:`UDPLITE_RECV_CSCOV`.
6+
Patch by Gabe Appleton.

Modules/socketmodule.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7737,6 +7737,17 @@ PyInit__socket(void)
77377737
#else
77387738
PyModule_AddIntConstant(m, "IPPROTO_UDP", 17);
77397739
#endif
7740+
#ifdef IPPROTO_UDPLITE
7741+
PyModule_AddIntMacro(m, IPPROTO_UDPLITE);
7742+
#ifndef UDPLITE_SEND_CSCOV
7743+
#define UDPLITE_SEND_CSCOV 10
7744+
#endif
7745+
PyModule_AddIntMacro(m, UDPLITE_SEND_CSCOV);
7746+
#ifndef UDPLITE_RECV_CSCOV
7747+
#define UDPLITE_RECV_CSCOV 11
7748+
#endif
7749+
PyModule_AddIntMacro(m, UDPLITE_RECV_CSCOV);
7750+
#endif
77407751
#ifdef IPPROTO_IDP
77417752
PyModule_AddIntMacro(m, IPPROTO_IDP);
77427753
#endif

0 commit comments

Comments
 (0)