Skip to content

Commit 9889d06

Browse files
committed
fix: attempt to connect to all given addresses
1 parent 69edbea commit 9889d06

File tree

1 file changed

+53
-34
lines changed

1 file changed

+53
-34
lines changed

pychromecast/socket_client.py

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,50 @@ def __init__(
229229

230230
self.receiver_controller.register_status_listener(self)
231231

232+
def _connect_to_host(self, host: str, port: int) -> socket.socket:
233+
234+
self.logger.debug(
235+
"[%s(%s):%s] Connecting to %s:%s",
236+
self.fn or "",
237+
self.host,
238+
self.port,
239+
self.host,
240+
self.port,
241+
)
242+
243+
addrinfo = socket.getaddrinfo(
244+
host,
245+
port,
246+
family=socket.AF_UNSPEC,
247+
type=socket.SOCK_STREAM,
248+
flags=(socket.AI_ADDRCONFIG | socket.AI_V4MAPPED),
249+
proto=socket.IPPROTO_TCP,
250+
)
251+
252+
# This should preferable be a happy eyeballs implementation
253+
for info in addrinfo:
254+
try:
255+
result = socket.socket(family=info[0], type=info[1], proto=info[2])
256+
try:
257+
configure_socket(result)
258+
result.settimeout(self.timeout)
259+
result.connect(info[4])
260+
except BaseException:
261+
result.close()
262+
raise
263+
264+
self.logger.debug("Connected using tuple: %s", info)
265+
return result
266+
except Exception:
267+
self.logger.debug(
268+
"Failed to connect using tuple: %s", info, exc_info=True
269+
)
270+
271+
else:
272+
raise ChromecastConnectionError(
273+
"All connection attempts failed, see debug log for details"
274+
)
275+
232276
def initialize_connection( # pylint:disable=too-many-statements, too-many-branches
233277
self,
234278
) -> None:
@@ -293,11 +337,6 @@ def mdns_backoff(
293337
self.socket = None
294338
self.remote_selector_key = None
295339

296-
self.socket = new_socket()
297-
self.remote_selector_key = self.selector.register(
298-
self.socket, selectors.EVENT_READ
299-
)
300-
self.socket.settimeout(self.timeout)
301340
self._report_connection_status(
302341
ConnectionStatus(
303342
CONNECTION_STATUS_CONNECTING,
@@ -349,24 +388,11 @@ def mdns_backoff(
349388
# try next service
350389
continue
351390

352-
self.logger.debug(
353-
"[%s(%s):%s] Connecting to %s:%s",
354-
self.fn or "",
355-
self.host,
356-
self.port,
357-
self.host,
358-
self.port,
391+
self.socket = self._connect_to_host(host, port)
392+
self.remote_selector_key = self.selector.register(
393+
self.socket, selectors.EVENT_READ
359394
)
360395

361-
mapped_host = socket.getaddrinfo(
362-
self.host,
363-
self.port,
364-
self.socket.family,
365-
socket.SOCK_STREAM,
366-
flags=(socket.AI_ADDRCONFIG | socket.AI_V4MAPPED),
367-
)[0][4]
368-
369-
self.socket.connect(mapped_host)
370396
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
371397
context.check_hostname = False
372398
context.verify_mode = ssl.CERT_NONE
@@ -404,7 +430,7 @@ def mdns_backoff(
404430
# OSError raised if connecting to the socket fails, NotConnected raised
405431
# if another thread tries - and fails - to send a message before the
406432
# calls to receiver_controller and heartbeat_controller.
407-
except (OSError, NotConnected) as err:
433+
except (OSError, NotConnected, ChromecastConnectionError) as err:
408434
self.connecting = True
409435
if self.stop.is_set():
410436
self.logger.error(
@@ -1096,22 +1122,17 @@ def receive_message(self, message: CastMessage, data: dict) -> bool:
10961122
return False
10971123

10981124

1099-
def new_socket() -> socket.socket:
1125+
def configure_socket(new_socket: socket.socket):
11001126
"""
11011127
Create a new socket with OS-specific parameters
11021128
11031129
Try to set SO_REUSEPORT for BSD-flavored systems if it's an option.
11041130
Catches errors if not.
11051131
"""
1106-
try:
1107-
new_sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
1108-
# ensuring dual-stack
1109-
new_sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
1110-
except OSError:
1111-
# falling back to IPv4 on systems without IPv6
1112-
new_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1132+
if new_socket.family == socket.AF_INET6:
1133+
new_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
11131134

1114-
new_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1135+
new_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
11151136

11161137
try:
11171138
# noinspection PyUnresolvedReferences
@@ -1120,10 +1141,8 @@ def new_socket() -> socket.socket:
11201141
pass
11211142
else:
11221143
try:
1123-
new_sock.setsockopt(socket.SOL_SOCKET, reuseport, 1)
1144+
new_socket.setsockopt(socket.SOL_SOCKET, reuseport, 1)
11241145
except (OSError, socket.error) as err:
11251146
# OSError on python 3, socket.error on python 2
11261147
if err.errno != errno.ENOPROTOOPT:
11271148
raise
1128-
1129-
return new_sock

0 commit comments

Comments
 (0)