@@ -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 ) -> None :
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