Skip to content

Commit 3a3330d

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 3a3330d

1 file changed

Lines changed: 23 additions & 2 deletions

File tree

journalpump/senders/websocket.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
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
1819
import time
19-
import websockets
20+
import websockets.client
21+
import websockets.exceptions
2022

2123

2224
@enum.unique
@@ -163,6 +165,8 @@ async def websocket_connect_coro(self):
163165

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

180200
ws_compr = None if self.websocket_compression == WebsocketCompression.none else str(self.websocket_compression)
181-
return await websockets.connect( # pylint:disable=no-member
201+
return await websockets.client.connect(
182202
self.websocket_uri,
203+
host=preferred_host,
183204
ssl=ssl_context,
184205
compression=ws_compr,
185206
extra_headers=headers,

0 commit comments

Comments
 (0)