Skip to content

Commit 8d120f7

Browse files
bpo-28724: Add methods send_fds and recv_fds to the socket module (GH-12889)
The socket module now has the socket.send_fds() and socket.recv.fds() functions. Contributed by Joannah Nanjekye, Shinya Okano (original patch) and Victor Stinner. Co-Authored-By: Victor Stinner <[email protected]>
1 parent 58ab134 commit 8d120f7

File tree

5 files changed

+105
-1
lines changed

5 files changed

+105
-1
lines changed

Doc/library/socket.rst

100644100755
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,6 +1584,29 @@ to sockets.
15841584

15851585
.. versionadded:: 3.6
15861586

1587+
.. method:: socket.send_fds(sock, buffers, fds[, flags[, address]])
1588+
1589+
Send the list of file descriptors *fds* over an :const:`AF_UNIX` socket.
1590+
The *fds* parameter is a sequence of file descriptors.
1591+
Consult :meth:`sendmsg` for the documentation of these parameters.
1592+
1593+
.. availability:: Unix supporting :meth:`~socket.sendmsg` and :const:`SCM_RIGHTS` mechanism.
1594+
1595+
.. versionadded:: 3.9
1596+
1597+
.. method:: socket.recv_fds(sock, bufsize, maxfds[, flags])
1598+
1599+
Receive up to *maxfds* file descriptors. Return ``(msg, list(fds), flags, addr)``. Consult
1600+
:meth:`recvmsg` for the documentation of these parameters.
1601+
1602+
.. availability:: Unix supporting :meth:`~socket.recvmsg` and :const:`SCM_RIGHTS` mechanism.
1603+
1604+
.. versionadded:: 3.9
1605+
1606+
.. note::
1607+
1608+
Any truncated integers at the end of the list of file descriptors.
1609+
15871610
.. method:: socket.sendfile(file, offset=0, count=None)
15881611

15891612
Send a file until EOF is reached by using high-performance

Doc/whatsnew/3.8.rst

100644100755
File mode changed.

Lib/socket.py

100644100755
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
socket() -- create a new socket object
1313
socketpair() -- create a pair of new socket objects [*]
1414
fromfd() -- create a socket object from an open file descriptor [*]
15+
send_fds() -- Send file descriptor to the socket.
16+
recv_fds() -- Recieve file descriptors from the socket.
1517
fromshare() -- create a socket object from data received from socket.share() [*]
1618
gethostname() -- return the current hostname
1719
gethostbyname() -- map a hostname to its IP number
@@ -542,6 +544,40 @@ def fromfd(fd, family, type, proto=0):
542544
nfd = dup(fd)
543545
return socket(family, type, proto, nfd)
544546

547+
if hasattr(_socket.socket, "sendmsg"):
548+
import array
549+
550+
def send_fds(sock, buffers, fds, flags=0, address=None):
551+
""" send_fds(sock, buffers, fds[, flags[, address]]) -> integer
552+
553+
Send the list of file descriptors fds over an AF_UNIX socket.
554+
"""
555+
return sock.sendmsg(buffers, [(_socket.SOL_SOCKET,
556+
_socket.SCM_RIGHTS, array.array("i", fds))])
557+
__all__.append("send_fds")
558+
559+
if hasattr(_socket.socket, "recvmsg"):
560+
import array
561+
562+
def recv_fds(sock, bufsize, maxfds, flags=0):
563+
""" recv_fds(sock, bufsize, maxfds[, flags]) -> (data, list of file
564+
descriptors, msg_flags, address)
565+
566+
Receive up to maxfds file descriptors returning the message
567+
data and a list containing the descriptors.
568+
"""
569+
# Array of ints
570+
fds = array.array("i")
571+
msg, ancdata, flags, addr = sock.recvmsg(bufsize,
572+
_socket.CMSG_LEN(maxfds * fds.itemsize))
573+
for cmsg_level, cmsg_type, cmsg_data in ancdata:
574+
if (cmsg_level == _socket.SOL_SOCKET and cmsg_type == _socket.SCM_RIGHTS):
575+
fds.frombytes(cmsg_data[:
576+
len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
577+
578+
return msg, list(fds), flags, addr
579+
__all__.append("recv_fds")
580+
545581
if hasattr(_socket.socket, "share"):
546582
def fromshare(info):
547583
""" fromshare(info) -> socket object

Lib/test/test_socket.py

100644100755
Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6436,11 +6436,53 @@ def test_dual_stack_client_v6(self):
64366436
self.echo_server(sock)
64376437
self.echo_client(("::1", port), socket.AF_INET6)
64386438

6439+
@requireAttrs(socket, "send_fds")
6440+
@requireAttrs(socket, "recv_fds")
6441+
@requireAttrs(socket, "AF_UNIX")
6442+
class SendRecvFdsTests(unittest.TestCase):
6443+
def testSendAndRecvFds(self):
6444+
def close_pipes(pipes):
6445+
for fd1, fd2 in pipes:
6446+
os.close(fd1)
6447+
os.close(fd2)
6448+
6449+
def close_fds(fds):
6450+
for fd in fds:
6451+
os.close(fd)
6452+
6453+
# send 10 file descriptors
6454+
pipes = [os.pipe() for _ in range(10)]
6455+
self.addCleanup(close_pipes, pipes)
6456+
fds = [rfd for rfd, wfd in pipes]
6457+
6458+
# use a UNIX socket pair to exchange file descriptors locally
6459+
sock1, sock2 = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
6460+
with sock1, sock2:
6461+
socket.send_fds(sock1, [MSG], fds)
6462+
# request more data and file descriptors than expected
6463+
msg, fds2, flags, addr = socket.recv_fds(sock2, len(MSG) * 2, len(fds) * 2)
6464+
self.addCleanup(close_fds, fds2)
6465+
6466+
self.assertEqual(msg, MSG)
6467+
self.assertEqual(len(fds2), len(fds))
6468+
self.assertEqual(flags, 0)
6469+
# don't test addr
6470+
6471+
# test that file descriptors are connected
6472+
for index, fds in enumerate(pipes):
6473+
rfd, wfd = fds
6474+
os.write(wfd, str(index).encode())
6475+
6476+
for index, rfd in enumerate(fds2):
6477+
data = os.read(rfd, 100)
6478+
self.assertEqual(data, str(index).encode())
6479+
64396480

64406481
def test_main():
64416482
tests = [GeneralModuleTests, BasicTCPTest, TCPCloserTest, TCPTimeoutTest,
64426483
TestExceptions, BufferIOTest, BasicTCPTest2, BasicUDPTest,
6443-
UDPTimeoutTest, CreateServerTest, CreateServerFunctionalTest]
6484+
UDPTimeoutTest, CreateServerTest, CreateServerFunctionalTest,
6485+
SendRecvFdsTests]
64446486

64456487
tests.extend([
64466488
NonBlockingTCPTests,
@@ -6513,5 +6555,6 @@ def test_main():
65136555
support.run_unittest(*tests)
65146556
support.threading_cleanup(*thread_info)
65156557

6558+
65166559
if __name__ == "__main__":
65176560
test_main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The socket module now has the :func:`socket.send_fds` and :func:`socket.recv.fds` methods.
2+
Contributed by Joannah Nanjekye, Shinya Okano and Victor Stinner.

0 commit comments

Comments
 (0)