Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit 8cd5165

Browse files
1st1miss-islington
authored andcommitted
bpo-37027: Return a proxy socket object from transp.get_extra_info('socket') (pythonGH-13530)
Return a safe to use proxy socket object from `transport.get_extra_info('socket')` https://bugs.python.org/issue37027
1 parent 674ee12 commit 8cd5165

File tree

7 files changed

+220
-9
lines changed

7 files changed

+220
-9
lines changed

Lib/asyncio/base_events.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from . import staggered
4646
from . import tasks
4747
from . import transports
48+
from . import trsock
4849
from .log import logger
4950

5051

@@ -319,8 +320,8 @@ def is_serving(self):
319320
@property
320321
def sockets(self):
321322
if self._sockets is None:
322-
return []
323-
return list(self._sockets)
323+
return ()
324+
return tuple(trsock.TransportSocket(s) for s in self._sockets)
324325

325326
def close(self):
326327
sockets = self._sockets

Lib/asyncio/proactor_events.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from . import protocols
2020
from . import sslproto
2121
from . import transports
22+
from . import trsock
2223
from .log import logger
2324

2425

@@ -454,7 +455,7 @@ def __init__(self, loop, sock, protocol, waiter=None,
454455
base_events._set_nodelay(sock)
455456

456457
def _set_extra(self, sock):
457-
self._extra['socket'] = sock
458+
self._extra['socket'] = trsock.TransportSocket(sock)
458459

459460
try:
460461
self._extra['sockname'] = sock.getsockname()
@@ -679,7 +680,7 @@ def loop(f=None):
679680
self.call_exception_handler({
680681
'message': 'Accept failed on a socket',
681682
'exception': exc,
682-
'socket': sock,
683+
'socket': trsock.TransportSocket(sock),
683684
})
684685
sock.close()
685686
elif self._debug:

Lib/asyncio/selector_events.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from . import protocols
2626
from . import sslproto
2727
from . import transports
28+
from . import trsock
2829
from .log import logger
2930

3031

@@ -171,7 +172,7 @@ def _accept_connection(
171172
self.call_exception_handler({
172173
'message': 'socket.accept() out of system resource',
173174
'exception': exc,
174-
'socket': sock,
175+
'socket': trsock.TransportSocket(sock),
175176
})
176177
self._remove_reader(sock.fileno())
177178
self.call_later(constants.ACCEPT_RETRY_DELAY,
@@ -603,7 +604,7 @@ class _SelectorTransport(transports._FlowControlMixin,
603604

604605
def __init__(self, loop, sock, protocol, extra=None, server=None):
605606
super().__init__(extra, loop)
606-
self._extra['socket'] = sock
607+
self._extra['socket'] = trsock.TransportSocket(sock)
607608
try:
608609
self._extra['sockname'] = sock.getsockname()
609610
except OSError:

Lib/asyncio/trsock.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import socket
2+
import warnings
3+
4+
5+
class TransportSocket:
6+
7+
"""A socket-like wrapper for exposing real transport sockets.
8+
9+
These objects can be safely returned by APIs like
10+
`transport.get_extra_info('socket')`. All potentially disruptive
11+
operations (like "socket.close()") are banned.
12+
"""
13+
14+
__slots__ = ('_sock',)
15+
16+
def __init__(self, sock: socket.socket):
17+
self._sock = sock
18+
19+
def _na(self, what):
20+
warnings.warn(
21+
f"Using {what} on sockets returned from get_extra_info('socket') "
22+
f"will be prohibited in asyncio 3.9. Please report your use case "
23+
f"to bugs.python.org.",
24+
DeprecationWarning, source=self)
25+
26+
@property
27+
def family(self):
28+
return self._sock.family
29+
30+
@property
31+
def type(self):
32+
return self._sock.type
33+
34+
@property
35+
def proto(self):
36+
return self._sock.proto
37+
38+
def __repr__(self):
39+
s = (
40+
f"<asyncio.TransportSocket fd={self.fileno()}, "
41+
f"family={self.family!s}, type={self.type!s}, "
42+
f"proto={self.proto}"
43+
)
44+
45+
if self.fileno() != -1:
46+
try:
47+
laddr = self.getsockname()
48+
if laddr:
49+
s = f"{s}, laddr={laddr}"
50+
except socket.error:
51+
pass
52+
try:
53+
raddr = self.getpeername()
54+
if raddr:
55+
s = f"{s}, raddr={raddr}"
56+
except socket.error:
57+
pass
58+
59+
return f"{s}>"
60+
61+
def __getstate__(self):
62+
raise TypeError("Cannot serialize asyncio.TransportSocket object")
63+
64+
def fileno(self):
65+
return self._sock.fileno()
66+
67+
def dup(self):
68+
return self._sock.dup()
69+
70+
def get_inheritable(self):
71+
return self._sock.get_inheritable()
72+
73+
def shutdown(self, how):
74+
# asyncio doesn't currently provide a high-level transport API
75+
# to shutdown the connection.
76+
self._sock.shutdown(how)
77+
78+
def getsockopt(self, *args, **kwargs):
79+
return self._sock.getsockopt(*args, **kwargs)
80+
81+
def setsockopt(self, *args, **kwargs):
82+
self._sock.setsockopt(*args, **kwargs)
83+
84+
def getpeername(self):
85+
return self._sock.getpeername()
86+
87+
def getsockname(self):
88+
return self._sock.getsockname()
89+
90+
def getsockbyname(self):
91+
return self._sock.getsockbyname()
92+
93+
def accept(self):
94+
self._na('accept() method')
95+
return self._sock.accept()
96+
97+
def connect(self, *args, **kwargs):
98+
self._na('connect() method')
99+
return self._sock.connect(*args, **kwargs)
100+
101+
def connect_ex(self, *args, **kwargs):
102+
self._na('connect_ex() method')
103+
return self._sock.connect_ex(*args, **kwargs)
104+
105+
def bind(self, *args, **kwargs):
106+
self._na('bind() method')
107+
return self._sock.bind(*args, **kwargs)
108+
109+
def ioctl(self, *args, **kwargs):
110+
self._na('ioctl() method')
111+
return self._sock.ioctl(*args, **kwargs)
112+
113+
def listen(self, *args, **kwargs):
114+
self._na('listen() method')
115+
return self._sock.listen(*args, **kwargs)
116+
117+
def makefile(self):
118+
self._na('makefile() method')
119+
return self._sock.makefile()
120+
121+
def sendfile(self, *args, **kwargs):
122+
self._na('sendfile() method')
123+
return self._sock.sendfile(*args, **kwargs)
124+
125+
def close(self):
126+
self._na('close() method')
127+
return self._sock.close()
128+
129+
def detach(self):
130+
self._na('detach() method')
131+
return self._sock.detach()
132+
133+
def sendmsg_afalg(self, *args, **kwargs):
134+
self._na('sendmsg_afalg() method')
135+
return self._sock.sendmsg_afalg(*args, **kwargs)
136+
137+
def sendmsg(self, *args, **kwargs):
138+
self._na('sendmsg() method')
139+
return self._sock.sendmsg(*args, **kwargs)
140+
141+
def sendto(self, *args, **kwargs):
142+
self._na('sendto() method')
143+
return self._sock.sendto(*args, **kwargs)
144+
145+
def send(self, *args, **kwargs):
146+
self._na('send() method')
147+
return self._sock.send(*args, **kwargs)
148+
149+
def sendall(self, *args, **kwargs):
150+
self._na('sendall() method')
151+
return self._sock.sendall(*args, **kwargs)
152+
153+
def set_inheritable(self, *args, **kwargs):
154+
self._na('set_inheritable() method')
155+
return self._sock.set_inheritable(*args, **kwargs)
156+
157+
def share(self, process_id):
158+
self._na('share() method')
159+
return self._sock.share(process_id)
160+
161+
def recv_into(self, *args, **kwargs):
162+
self._na('recv_into() method')
163+
return self._sock.recv_into(*args, **kwargs)
164+
165+
def recvfrom_into(self, *args, **kwargs):
166+
self._na('recvfrom_into() method')
167+
return self._sock.recvfrom_into(*args, **kwargs)
168+
169+
def recvmsg_into(self, *args, **kwargs):
170+
self._na('recvmsg_into() method')
171+
return self._sock.recvmsg_into(*args, **kwargs)
172+
173+
def recvmsg(self, *args, **kwargs):
174+
self._na('recvmsg() method')
175+
return self._sock.recvmsg(*args, **kwargs)
176+
177+
def recvfrom(self, *args, **kwargs):
178+
self._na('recvfrom() method')
179+
return self._sock.recvfrom(*args, **kwargs)
180+
181+
def recv(self, *args, **kwargs):
182+
self._na('recv() method')
183+
return self._sock.recv(*args, **kwargs)
184+
185+
def settimeout(self, value):
186+
if value == 0:
187+
return
188+
raise ValueError(
189+
'settimeout(): only 0 timeout is allowed on transport sockets')
190+
191+
def gettimeout(self):
192+
return 0
193+
194+
def setblocking(self, flag):
195+
if not flag:
196+
return
197+
raise ValueError(
198+
'setblocking(): transport sockets cannot be blocking')
199+
200+
def __enter__(self):
201+
self._na('context manager protocol')
202+
return self._sock.__enter__()
203+
204+
def __exit__(self, *err):
205+
self._na('context manager protocol')
206+
return self._sock.__exit__(*err)

Lib/test/test_asyncio/test_events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,7 @@ def connection_made(self, transport):
11181118
f = self.loop.create_server(TestMyProto, sock=sock_ob)
11191119
server = self.loop.run_until_complete(f)
11201120
sock = server.sockets[0]
1121-
self.assertIs(sock, sock_ob)
1121+
self.assertEqual(sock.fileno(), sock_ob.fileno())
11221122

11231123
host, port = sock.getsockname()
11241124
self.assertEqual(host, '0.0.0.0')

Lib/test/test_asyncio/test_server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ async def main(srv):
5858
with self.tcp_client(lambda sock: client(sock, addr)):
5959
self.loop.run_until_complete(main_task)
6060

61-
self.assertEqual(srv.sockets, [])
61+
self.assertEqual(srv.sockets, ())
6262

6363
self.assertIsNone(srv._sockets)
6464
self.assertIsNone(srv._waiters)
@@ -111,7 +111,7 @@ async def main(srv):
111111
with self.unix_client(lambda sock: client(sock, addr)):
112112
self.loop.run_until_complete(main_task)
113113

114-
self.assertEqual(srv.sockets, [])
114+
self.assertEqual(srv.sockets, ())
115115

116116
self.assertIsNone(srv._sockets)
117117
self.assertIsNone(srv._waiters)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Return safe to use proxy socket object from
2+
transport.get_extra_info('socket')

0 commit comments

Comments
 (0)