Skip to content

Commit 2825aa4

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 c1c196d commit 2825aa4

1 file changed

Lines changed: 24 additions & 0 deletions

File tree

journalpump/senders/websocket.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import contextlib
1414
import enum
1515
import logging
16+
import random
1617
import snappy # pylint: disable=import-error
1718
import socket
1819
import ssl
@@ -164,6 +165,8 @@ async def websocket_connect_coro(self):
164165

165166
sock = None
166167
url_parsed = urlparse(self.websocket_uri)
168+
preferred_host = None
169+
167170
if self.socks5_proxy:
168171
socks_url_parsed = urlparse(self.socks5_proxy_url)
169172
self.log.info(
@@ -177,6 +180,24 @@ async def websocket_connect_coro(self):
177180
socks_url_parsed.hostname,
178181
socks_url_parsed.port,
179182
)
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+
# getaddrinfo returns 5-types (family, type, proto, canonname, sockaddr)
190+
# sockaddr is family dependent, but leads with address for both the IPv6 and IPv4 families
191+
sockaddr[0]
192+
for _, _, _, _, sockaddr in await self.websocket_loop.getaddrinfo(
193+
url_parsed.hostname, 0, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP
194+
)
195+
]
196+
if present_addrs:
197+
preferred_host = random.choice(present_addrs)
198+
else:
199+
# We couldn't resolve a suitable name, fallback to async.loop.create_connection() name handling
200+
preferred_host = url_parsed.hostname
180201

181202
# In order to support version transition in websockects, we generated kwargs dynamically
182203
connect_kwargs = {
@@ -210,6 +231,9 @@ async def websocket_connect_coro(self):
210231
else:
211232
connect_kwargs["extra_headers"] = headers
212233

234+
if preferred_host:
235+
connect_kwargs["host"] = preferred_host
236+
213237
return await websockets.connect(self.websocket_uri, **connect_kwargs)
214238

215239
async def websocket_connect(self, *, timeout=30):

0 commit comments

Comments
 (0)