Skip to content

Commit 392c136

Browse files
committed
senders: resolve and use a random address on websockets connect
Resolve websocket remote address and pick a random one as an explicit host to connect. The combination of asyncio websockets and asyncio create_connect() have some overlapping timeouts that lead to connection attempts to run through the whole list of targets returned by getaddrinfo(). This breaks connectivity in some dual stack IPv6/IPv4 cases, when either one of the address family has e.g. routing problems. We can fairly safely pick one at random here; we have retry logic built around the connections anyway that eventually lead to connected state.
1 parent c62e2ea commit 392c136

1 file changed

Lines changed: 21 additions & 1 deletion

File tree

journalpump/senders/websocket.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import contextlib
1313
import enum
1414
import logging
15+
import random
1516
import snappy # pylint: disable=import-error
1617
import socket
1718
import ssl
@@ -163,6 +164,8 @@ async def websocket_connect_coro(self):
163164

164165
sock = None
165166
url_parsed = urlparse(self.websocket_uri)
167+
preferred_host = None
168+
166169
if self.socks5_proxy:
167170
socks_url_parsed = urlparse(self.socks5_proxy_url)
168171
self.log.info(
@@ -176,10 +179,27 @@ async def websocket_connect_coro(self):
176179
socks_url_parsed.hostname,
177180
socks_url_parsed.port,
178181
)
182+
else:
183+
# Resolve hostname and pick one address at random
184+
# Websockets.connect() and underlying asyncio.loop.create_connection() have some overlapping timeouts
185+
# that lead to little bit difficulties with working through all addresses returned by getaddrinfo.
186+
# We pick one at random here, and rely our own outer loops to handle retries.
187+
present_addrs = [
188+
gai_result[4][0]
189+
for gai_result in await self.websocket_loop.getaddrinfo(
190+
url_parsed.hostname, 0, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP
191+
)
192+
]
193+
if present_addrs:
194+
preferred_host = random.choice(present_addrs)
195+
else:
196+
# We couldn't resolve a suitable name, fallback to async.loop.create_connection() name handling
197+
preferred_host = url_parsed.hostname
179198

180199
ws_compr = None if self.websocket_compression == WebsocketCompression.none else str(self.websocket_compression)
181-
return await websockets.connect( # pylint:disable=no-member
200+
return await websockets.connect(
182201
self.websocket_uri,
202+
host=preferred_host,
183203
ssl=ssl_context,
184204
compression=ws_compr,
185205
extra_headers=headers,

0 commit comments

Comments
 (0)