From 16c274dbffef6ff23f6044f6cbb40b3c12d01054 Mon Sep 17 00:00:00 2001 From: Drake Aiman Date: Tue, 13 May 2025 11:18:54 -0700 Subject: [PATCH 1/5] Add initial support for IPv6 --- src/debugpy/adapter/__main__.py | 14 +++++--- src/debugpy/adapter/clients.py | 19 +++++----- src/debugpy/adapter/launchers.py | 7 ++-- src/debugpy/adapter/servers.py | 4 +-- src/debugpy/common/sockets.py | 60 +++++++++++++++++++++++++++----- src/debugpy/launcher/__init__.py | 3 +- src/debugpy/launcher/__main__.py | 7 ++-- src/debugpy/server/api.py | 10 +++--- src/debugpy/server/cli.py | 7 ++-- tests/debug/comms.py | 2 +- tests/debug/session.py | 3 +- tests/debugpy/server/test_cli.py | 6 ++-- tests/debugpy/test_attach.py | 19 ++++++---- 13 files changed, 113 insertions(+), 48 deletions(-) diff --git a/src/debugpy/adapter/__main__.py b/src/debugpy/adapter/__main__.py index 93cd05f6d..43a7e5118 100644 --- a/src/debugpy/adapter/__main__.py +++ b/src/debugpy/adapter/__main__.py @@ -65,9 +65,10 @@ def main(): else: endpoints["client"] = {"host": client_host, "port": client_port} + localhost = sockets.get_default_localhost() if args.for_server is not None: try: - server_host, server_port = servers.serve() + server_host, server_port = servers.serve(localhost) except Exception as exc: endpoints = {"error": "Can't listen for server connections: " + str(exc)} else: @@ -80,10 +81,11 @@ def main(): ) try: - sock = sockets.create_client() + ipv6 = True if localhost.count(":") > 1 else False + sock = sockets.create_client(ipv6) try: sock.settimeout(None) - sock.connect(("127.0.0.1", args.for_server)) + sock.connect((localhost, args.for_server)) sock_io = sock.makefile("wb", 0) try: sock_io.write(json.dumps(endpoints).encode("utf-8")) @@ -137,6 +139,10 @@ def delete_listener_file(): def _parse_argv(argv): + from debugpy.common import sockets + + host = sockets.get_default_localhost() + parser = argparse.ArgumentParser() parser.add_argument( @@ -154,7 +160,7 @@ def _parse_argv(argv): parser.add_argument( "--host", type=str, - default="127.0.0.1", + default=host, metavar="HOST", help="start the adapter in debugServer mode on the specified host", ) diff --git a/src/debugpy/adapter/clients.py b/src/debugpy/adapter/clients.py index 0b4a870a7..186c1ab6d 100644 --- a/src/debugpy/adapter/clients.py +++ b/src/debugpy/adapter/clients.py @@ -404,7 +404,8 @@ def property_or_debug_option(prop_name, flag_name): self._forward_terminate_request = on_terminate == "KeyboardInterrupt" launcher_path = request("debugLauncherPath", os.path.dirname(launcher.__file__)) - adapter_host = request("debugAdapterHost", "127.0.0.1") + localhost = sockets.get_default_localhost() + adapter_host = request("debugAdapterHost", localhost) try: servers.serve(adapter_host) @@ -477,15 +478,16 @@ def attach_request(self, request): raise request.isnt_valid( 'Multiple concurrent "listen" sessions are not supported' ) - host = listen("host", "127.0.0.1") + localhost = sockets.get_default_localhost() + host = listen("host", localhost) port = listen("port", int) adapter.access_token = None self.restart_requested = request("restart", False) host, port = servers.serve(host, port) else: if not servers.is_serving(): - servers.serve() - host, port = servers.listener.getsockname() + servers.serve(host) + host, port = servers.listener.getsockname()[:2] # There are four distinct possibilities here. # @@ -718,7 +720,7 @@ def report_sockets(self): } for listener in [clients.listener, launchers.listener, servers.listener] if listener is not None - for (host, port) in [listener.getsockname()] + for (host, port) in [listener.getsockname()[:2]] ] self.channel.send_event( "debugpySockets", @@ -759,10 +761,11 @@ def notify_of_subprocess(self, conn): if "connect" not in body: body["connect"] = {} if "host" not in body["connect"]: - body["connect"]["host"] = host if host is not None else "127.0.0.1" + localhost = sockets.get_default_localhost() + body["connect"]["host"] = host if host is not None else localhost if "port" not in body["connect"]: if port is None: - _, port = listener.getsockname() + _, port = listener.getsockname()[:2] body["connect"]["port"] = port if self.capabilities["supportsStartDebuggingRequest"]: @@ -779,7 +782,7 @@ def serve(host, port): global listener listener = sockets.serve("Client", Client, host, port) sessions.report_sockets() - return listener.getsockname() + return listener.getsockname()[:2] def stop_serving(): diff --git a/src/debugpy/adapter/launchers.py b/src/debugpy/adapter/launchers.py index 38a990d76..6361ff8aa 100644 --- a/src/debugpy/adapter/launchers.py +++ b/src/debugpy/adapter/launchers.py @@ -89,7 +89,7 @@ def spawn_debuggee( arguments = dict(start_request.arguments) if not session.no_debug: - _, arguments["port"] = servers.listener.getsockname() + _, arguments["port"] = servers.listener.getsockname()[:2] arguments["adapterAccessToken"] = adapter.access_token def on_launcher_connected(sock): @@ -108,10 +108,11 @@ def on_launcher_connected(sock): sessions.report_sockets() try: - launcher_host, launcher_port = listener.getsockname() + launcher_host, launcher_port = listener.getsockname()[:2] + localhost = sockets.get_default_localhost() launcher_addr = ( launcher_port - if launcher_host == "127.0.0.1" + if launcher_host == localhost else f"{launcher_host}:{launcher_port}" ) cmdline += [str(launcher_addr), "--"] diff --git a/src/debugpy/adapter/servers.py b/src/debugpy/adapter/servers.py index 307a2ce62..aa31a332c 100644 --- a/src/debugpy/adapter/servers.py +++ b/src/debugpy/adapter/servers.py @@ -395,7 +395,7 @@ def serve(host="127.0.0.1", port=0): global listener listener = sockets.serve("Server", Connection, host, port) sessions.report_sockets() - return listener.getsockname() + return listener.getsockname()[:2] def is_serving(): @@ -475,7 +475,7 @@ def dont_wait_for_first_connection(): def inject(pid, debugpy_args, on_output): - host, port = listener.getsockname() + host, port = listener.getsockname()[:2] cmdline = [ sys.executable, diff --git a/src/debugpy/common/sockets.py b/src/debugpy/common/sockets.py index ffcef80f6..2d3e39e9d 100644 --- a/src/debugpy/common/sockets.py +++ b/src/debugpy/common/sockets.py @@ -9,18 +9,60 @@ from debugpy.common import log from debugpy.common.util import hide_thread_from_debugger +def can_bind_ipv4_localhost(): + """Check if we can bind to IPv4 localhost.""" + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # Try to bind to IPv4 localhost on port 0 (any available port) + sock.bind(('127.0.0.1', 0)) + sock.close() + return True + except (socket.error, OSError): + return False + +def can_bind_ipv6_localhost(): + """Check if we can bind to IPv6 localhost.""" + try: + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # Try to bind to IPv6 localhost on port 0 (any available port) + sock.bind(('::1', 0)) + sock.close() + return True + except (socket.error, OSError, AttributeError): + return False + +def get_default_localhost(): + """ + Get the default localhost address. + Defaults to IPv4 '127.0.0.1', but falls back to IPv6 '::1' if IPv4 is unavailable. + """ + # First try IPv4 (preferred default) + if can_bind_ipv4_localhost(): + return '127.0.0.1' + + # Fall back to IPv6 if IPv4 is not available + if can_bind_ipv6_localhost(): + return '::1' + + # If neither works, still return IPv4 as a last resort + # (this is a very unusual situation) + return '127.0.0.1' + def create_server(host, port=0, backlog=socket.SOMAXCONN, timeout=None): """Return a local server socket listening on the given port.""" assert backlog > 0 if host is None: - host = "127.0.0.1" + host = get_default_localhost() if port is None: port = 0 + ipv6 = True if host.count(":") > 1 else False try: - server = _new_sock() + server = _new_sock(ipv6) if port != 0: # If binding to a specific port, make sure that the user doesn't have # to wait until the OS times out the socket to be able to use that port @@ -42,13 +84,14 @@ def create_server(host, port=0, backlog=socket.SOMAXCONN, timeout=None): return server -def create_client(): +def create_client(ipv6 = False): """Return a client socket that may be connected to a remote address.""" - return _new_sock() + return _new_sock(ipv6) -def _new_sock(): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) +def _new_sock(ipv6 = False): + address_family = socket.AF_INET6 if ipv6 else socket.AF_INET + sock = socket.socket(address_family, socket.SOCK_STREAM, socket.IPPROTO_TCP) # Set TCP keepalive on an open socket. # It activates after 1 second (TCP_KEEPIDLE,) of idleness, @@ -102,13 +145,14 @@ def serve(name, handler, host, port=0, backlog=socket.SOMAXCONN, timeout=None): log.reraise_exception( "Error listening for incoming {0} connections on {1}:{2}:", name, host, port ) - host, port = listener.getsockname() + host, port = listener.getsockname()[:2] log.info("Listening for incoming {0} connections on {1}:{2}...", name, host, port) def accept_worker(): while True: try: - sock, (other_host, other_port) = listener.accept() + sock, address = listener.accept() + other_host, other_port = address[:2] except (OSError, socket.error): # Listener socket has been closed. break diff --git a/src/debugpy/launcher/__init__.py b/src/debugpy/launcher/__init__.py index a6e0934f1..a89f190f3 100644 --- a/src/debugpy/launcher/__init__.py +++ b/src/debugpy/launcher/__init__.py @@ -23,7 +23,8 @@ def connect(host, port): log.info("Connecting to adapter at {0}:{1}", host, port) - sock = sockets.create_client() + ipv6 = True if host.count(":") > 1 else False + sock = sockets.create_client(ipv6) sock.connect((host, port)) adapter_host = host diff --git a/src/debugpy/launcher/__main__.py b/src/debugpy/launcher/__main__.py index cff18b5f1..7e17d4da8 100644 --- a/src/debugpy/launcher/__main__.py +++ b/src/debugpy/launcher/__main__.py @@ -14,7 +14,7 @@ def main(): from debugpy import launcher - from debugpy.common import log + from debugpy.common import log, sockets from debugpy.launcher import debuggee log.to_file(prefix="debugpy.launcher") @@ -38,9 +38,10 @@ def main(): # The first argument specifies the host/port on which the adapter is waiting # for launcher to connect. It's either host:port, or just port. adapter = launcher_argv[0] - host, sep, port = adapter.partition(":") + host, sep, port = adapter.rpartition(":") + host.strip("[]") if not sep: - host = "127.0.0.1" + host = sockets.get_default_localhost() port = adapter port = int(port) diff --git a/src/debugpy/server/api.py b/src/debugpy/server/api.py index 4b8cf9c54..fb76641bd 100644 --- a/src/debugpy/server/api.py +++ b/src/debugpy/server/api.py @@ -100,7 +100,8 @@ def debug(address, **kwargs): _, port = address except Exception: port = address - address = ("127.0.0.1", port) + localhost = sockets.get_default_localhost() + address = (localhost, port) try: port.__index__() # ensure it's int-like except Exception: @@ -143,8 +144,8 @@ def listen(address, settrace_kwargs, in_process_debug_adapter=False): # Multiple calls to listen() cause the debuggee to hang raise RuntimeError("debugpy.listen() has already been called on this process") + host, port = address if in_process_debug_adapter: - host, port = address log.info("Listening: pydevd without debugpy adapter: {0}:{1}", host, port) settrace_kwargs["patch_multiprocessing"] = False _settrace( @@ -161,13 +162,14 @@ def listen(address, settrace_kwargs, in_process_debug_adapter=False): server_access_token = codecs.encode(os.urandom(32), "hex").decode("ascii") try: - endpoints_listener = sockets.create_server("127.0.0.1", 0, timeout=30) + localhost = sockets.get_default_localhost() + endpoints_listener = sockets.create_server(localhost, 0, timeout=30) except Exception as exc: log.swallow_exception("Can't listen for adapter endpoints:") raise RuntimeError("can't listen for adapter endpoints: " + str(exc)) try: - endpoints_host, endpoints_port = endpoints_listener.getsockname() + endpoints_host, endpoints_port = endpoints_listener.getsockname()[:2] log.info( "Waiting for adapter endpoints on {0}:{1}...", endpoints_host, diff --git a/src/debugpy/server/cli.py b/src/debugpy/server/cli.py index 5e0520da8..a64f515aa 100644 --- a/src/debugpy/server/cli.py +++ b/src/debugpy/server/cli.py @@ -20,7 +20,7 @@ import debugpy import debugpy.server -from debugpy.common import log +from debugpy.common import log, sockets from debugpy.server import api @@ -104,9 +104,10 @@ def do(arg, it): # It's either host:port, or just port. value = next(it) - host, sep, port = value.partition(":") + host, sep, port = value.rpartition(":") + host = host.strip("[]") if not sep: - host = "127.0.0.1" + host = sockets.get_default_localhost() port = value try: port = int(port) diff --git a/tests/debug/comms.py b/tests/debug/comms.py index e690aceb3..0ffb3c5a3 100644 --- a/tests/debug/comms.py +++ b/tests/debug/comms.py @@ -25,7 +25,7 @@ def __str__(self): def listen(self): self._server_socket = sockets.create_server("127.0.0.1", 0, self.TIMEOUT) - _, self.port = self._server_socket.getsockname() + _, self.port = self._server_socket.getsockname()[:2] self._server_socket.listen(0) def accept_worker(): diff --git a/tests/debug/session.py b/tests/debug/session.py index 3a821f5bc..8e319848c 100644 --- a/tests/debug/session.py +++ b/tests/debug/session.py @@ -464,7 +464,8 @@ def connect_to_adapter(self, address): self.expected_adapter_sockets["client"]["port"] = port - sock = sockets.create_client() + ipv6 = True if host.count(":") > 1 else False + sock = sockets.create_client(ipv6) sock.connect(address) stream = messaging.JsonIOStream.from_socket(sock, name=self.adapter_id) diff --git a/tests/debugpy/server/test_cli.py b/tests/debugpy/server/test_cli.py index a098cda8b..9b64ee0e5 100644 --- a/tests/debugpy/server/test_cli.py +++ b/tests/debugpy/server/test_cli.py @@ -15,7 +15,6 @@ from debugpy.common import log from tests.patterns import some - @pytest.fixture def cli(pyfile): @pyfile @@ -89,7 +88,7 @@ def parse(args): # Test a combination of command line switches @pytest.mark.parametrize("target_kind", ["file", "module", "code"]) @pytest.mark.parametrize("mode", ["listen", "connect"]) -@pytest.mark.parametrize("address", ["8888", "localhost:8888"]) +@pytest.mark.parametrize("address", ["8888", "localhost:8888", "[::1]:8888"]) @pytest.mark.parametrize("wait_for_client", ["", "wait_for_client"]) @pytest.mark.parametrize("script_args", ["", "script_args"]) def test_targets(cli, target_kind, mode, address, wait_for_client, script_args): @@ -101,7 +100,8 @@ def test_targets(cli, target_kind, mode, address, wait_for_client, script_args): args = ["--" + mode, address] - host, sep, port = address.partition(":") + host, sep, port = address.rpartition(":") + host = host.strip("[]") if sep: expected_options["address"] = (host, int(port)) else: diff --git a/tests/debugpy/test_attach.py b/tests/debugpy/test_attach.py index c973b09bb..a2cb2fa1e 100644 --- a/tests/debugpy/test_attach.py +++ b/tests/debugpy/test_attach.py @@ -14,8 +14,9 @@ @pytest.mark.parametrize("stop_method", ["breakpoint", "pause"]) @pytest.mark.skipif(IS_PY312_OR_GREATER, reason="Flakey test on 312 and higher") @pytest.mark.parametrize("is_client_connected", ["is_client_connected", ""]) +@pytest.mark.parametrize("host", ["127.0.0.1", "::1"]) @pytest.mark.parametrize("wait_for_client", ["wait_for_client", pytest.param("", marks=pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Flakey test on Mac"))]) -def test_attach_api(pyfile, wait_for_client, is_client_connected, stop_method): +def test_attach_api(pyfile, host, wait_for_client, is_client_connected, stop_method): @pyfile def code_to_debug(): import debuggee @@ -58,7 +59,8 @@ def code_to_debug(): time.sleep(0.1) with debug.Session() as session: - host, port = runners.attach_connect.host, runners.attach_connect.port + host = runners.attach_connect.host if host == "127.0.0.1" else host + port = runners.attach_connect.port session.config.update({"connect": {"host": host, "port": port}}) backchannel = session.open_backchannel() @@ -102,7 +104,8 @@ def code_to_debug(): session.request_continue() -def test_multiple_listen_raises_exception(pyfile): +@pytest.mark.parametrize("host", ["127.0.0.1", "::1"]) +def test_multiple_listen_raises_exception(pyfile, host): @pyfile def code_to_debug(): import debuggee @@ -124,7 +127,8 @@ def code_to_debug(): debugpy.breakpoint() print("break") # @breakpoint - host, port = runners.attach_connect.host, runners.attach_connect.port + host = runners.attach_connect.host if host == "127.0.0.1" else host + port = runners.attach_connect.port with debug.Session() as session: backchannel = session.open_backchannel() session.spawn_debuggee( @@ -147,7 +151,6 @@ def code_to_debug(): assert backchannel.receive() == "listen_exception" session.request_continue() - @pytest.mark.parametrize("run", runners.all_attach_connect) def test_reattach(pyfile, target, run): @pyfile @@ -265,7 +268,8 @@ def before_request(command, arguments): session2.request_continue() -def test_cancel_wait(pyfile): +@pytest.mark.parametrize("host", ["127.0.0.1", "::1"]) +def test_cancel_wait(pyfile, host): @pyfile def code_to_debug(): import debugpy @@ -287,7 +291,8 @@ def cancel(): backchannel.send("exit") with debug.Session() as session: - host, port = runners.attach_connect.host, runners.attach_connect.port + host = runners.attach_connect.host if host == "127.0.0.1" else host + port = runners.attach_connect.port session.config.update({"connect": {"host": host, "port": port}}) session.expected_exit_code = None From 6ab314d30166d43fd10d4462923c8690d1dbeabd Mon Sep 17 00:00:00 2001 From: Drake Aiman Date: Tue, 20 May 2025 09:19:47 -0700 Subject: [PATCH 2/5] address comments (part 1) --- src/debugpy/adapter/__main__.py | 2 +- src/debugpy/adapter/clients.py | 2 +- src/debugpy/common/sockets.py | 17 ++++++++--------- src/debugpy/launcher/__init__.py | 2 +- tests/debug/session.py | 2 +- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/debugpy/adapter/__main__.py b/src/debugpy/adapter/__main__.py index 43a7e5118..a6705c069 100644 --- a/src/debugpy/adapter/__main__.py +++ b/src/debugpy/adapter/__main__.py @@ -81,7 +81,7 @@ def main(): ) try: - ipv6 = True if localhost.count(":") > 1 else False + ipv6 = localhost.count(":") > 1 sock = sockets.create_client(ipv6) try: sock.settimeout(None) diff --git a/src/debugpy/adapter/clients.py b/src/debugpy/adapter/clients.py index 186c1ab6d..2b11c7402 100644 --- a/src/debugpy/adapter/clients.py +++ b/src/debugpy/adapter/clients.py @@ -762,7 +762,7 @@ def notify_of_subprocess(self, conn): body["connect"] = {} if "host" not in body["connect"]: localhost = sockets.get_default_localhost() - body["connect"]["host"] = host if host is not None else localhost + body["connect"]["host"] = host or localhost if "port" not in body["connect"]: if port is None: _, port = listener.getsockname()[:2] diff --git a/src/debugpy/common/sockets.py b/src/debugpy/common/sockets.py index 2d3e39e9d..ee19f8278 100644 --- a/src/debugpy/common/sockets.py +++ b/src/debugpy/common/sockets.py @@ -15,7 +15,7 @@ def can_bind_ipv4_localhost(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Try to bind to IPv4 localhost on port 0 (any available port) - sock.bind(('127.0.0.1', 0)) + sock.bind(("127.0.0.1", 0)) sock.close() return True except (socket.error, OSError): @@ -27,28 +27,27 @@ def can_bind_ipv6_localhost(): sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Try to bind to IPv6 localhost on port 0 (any available port) - sock.bind(('::1', 0)) + sock.bind(("::1", 0)) sock.close() return True except (socket.error, OSError, AttributeError): return False def get_default_localhost(): - """ - Get the default localhost address. + """Get the default localhost address. Defaults to IPv4 '127.0.0.1', but falls back to IPv6 '::1' if IPv4 is unavailable. """ # First try IPv4 (preferred default) if can_bind_ipv4_localhost(): - return '127.0.0.1' + return "127.0.0.1" # Fall back to IPv6 if IPv4 is not available if can_bind_ipv6_localhost(): - return '::1' + return "::1" # If neither works, still return IPv4 as a last resort # (this is a very unusual situation) - return '127.0.0.1' + return "127.0.0.1" def create_server(host, port=0, backlog=socket.SOMAXCONN, timeout=None): @@ -59,7 +58,7 @@ def create_server(host, port=0, backlog=socket.SOMAXCONN, timeout=None): host = get_default_localhost() if port is None: port = 0 - ipv6 = True if host.count(":") > 1 else False + ipv6 = host.count(":") > 1 try: server = _new_sock(ipv6) @@ -84,7 +83,7 @@ def create_server(host, port=0, backlog=socket.SOMAXCONN, timeout=None): return server -def create_client(ipv6 = False): +def create_client(ipv6=False): """Return a client socket that may be connected to a remote address.""" return _new_sock(ipv6) diff --git a/src/debugpy/launcher/__init__.py b/src/debugpy/launcher/__init__.py index a89f190f3..677740262 100644 --- a/src/debugpy/launcher/__init__.py +++ b/src/debugpy/launcher/__init__.py @@ -23,7 +23,7 @@ def connect(host, port): log.info("Connecting to adapter at {0}:{1}", host, port) - ipv6 = True if host.count(":") > 1 else False + ipv6 = host.count(":") > 1 sock = sockets.create_client(ipv6) sock.connect((host, port)) adapter_host = host diff --git a/tests/debug/session.py b/tests/debug/session.py index 8e319848c..05abea62a 100644 --- a/tests/debug/session.py +++ b/tests/debug/session.py @@ -464,7 +464,7 @@ def connect_to_adapter(self, address): self.expected_adapter_sockets["client"]["port"] = port - ipv6 = True if host.count(":") > 1 else False + ipv6 = host.count(":") > 1 sock = sockets.create_client(ipv6) sock.connect(address) From f53cba25b3d72b039963b2b1a79343411f4b16aa Mon Sep 17 00:00:00 2001 From: Drake Aiman Date: Wed, 21 May 2025 08:31:24 -0700 Subject: [PATCH 3/5] quick clean up of missed fix suggestion --- src/debugpy/common/sockets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugpy/common/sockets.py b/src/debugpy/common/sockets.py index ee19f8278..e694c2a71 100644 --- a/src/debugpy/common/sockets.py +++ b/src/debugpy/common/sockets.py @@ -88,7 +88,7 @@ def create_client(ipv6=False): return _new_sock(ipv6) -def _new_sock(ipv6 = False): +def _new_sock(ipv6=False): address_family = socket.AF_INET6 if ipv6 else socket.AF_INET sock = socket.socket(address_family, socket.SOCK_STREAM, socket.IPPROTO_TCP) From 3ef28441edfc84ab3a2ff731693fd8505407befc Mon Sep 17 00:00:00 2001 From: Drake Aiman Date: Wed, 21 May 2025 20:54:26 -0700 Subject: [PATCH 4/5] Fix misassigned default serving server address in adapter client --- src/debugpy/adapter/clients.py | 4 ++-- src/debugpy/common/sockets.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/debugpy/adapter/clients.py b/src/debugpy/adapter/clients.py index 2b11c7402..917d0ae44 100644 --- a/src/debugpy/adapter/clients.py +++ b/src/debugpy/adapter/clients.py @@ -473,12 +473,12 @@ def attach_request(self, request): '"processId" and "subProcessId" are mutually exclusive' ) + localhost = sockets.get_default_localhost() if listen != (): if servers.is_serving(): raise request.isnt_valid( 'Multiple concurrent "listen" sessions are not supported' ) - localhost = sockets.get_default_localhost() host = listen("host", localhost) port = listen("port", int) adapter.access_token = None @@ -486,7 +486,7 @@ def attach_request(self, request): host, port = servers.serve(host, port) else: if not servers.is_serving(): - servers.serve(host) + servers.serve(localhost) host, port = servers.listener.getsockname()[:2] # There are four distinct possibilities here. diff --git a/src/debugpy/common/sockets.py b/src/debugpy/common/sockets.py index e694c2a71..0b1c8fadc 100644 --- a/src/debugpy/common/sockets.py +++ b/src/debugpy/common/sockets.py @@ -18,7 +18,7 @@ def can_bind_ipv4_localhost(): sock.bind(("127.0.0.1", 0)) sock.close() return True - except (socket.error, OSError): + except (socket.error, OSError, AttributeError): return False def can_bind_ipv6_localhost(): From 79860c58bef54c4b6a4f0c823794d6828b284180 Mon Sep 17 00:00:00 2001 From: Drake Aiman Date: Thu, 22 May 2025 11:55:35 -0700 Subject: [PATCH 5/5] add wrapper method to get host and port from`getsockname` --- src/debugpy/adapter/clients.py | 12 ++++++------ src/debugpy/adapter/launchers.py | 4 ++-- src/debugpy/adapter/servers.py | 4 ++-- src/debugpy/common/sockets.py | 11 ++++++++++- src/debugpy/server/api.py | 2 +- tests/debug/comms.py | 2 +- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/debugpy/adapter/clients.py b/src/debugpy/adapter/clients.py index 917d0ae44..4931b70c7 100644 --- a/src/debugpy/adapter/clients.py +++ b/src/debugpy/adapter/clients.py @@ -487,7 +487,7 @@ def attach_request(self, request): else: if not servers.is_serving(): servers.serve(localhost) - host, port = servers.listener.getsockname()[:2] + host, port = sockets.get_address(servers.listener) # There are four distinct possibilities here. # @@ -712,7 +712,7 @@ def disconnect(self): super().disconnect() def report_sockets(self): - sockets = [ + socks = [ { "host": host, "port": port, @@ -720,12 +720,12 @@ def report_sockets(self): } for listener in [clients.listener, launchers.listener, servers.listener] if listener is not None - for (host, port) in [listener.getsockname()[:2]] + for (host, port) in [sockets.get_address(listener)] ] self.channel.send_event( "debugpySockets", { - "sockets": sockets + "sockets": socks }, ) @@ -765,7 +765,7 @@ def notify_of_subprocess(self, conn): body["connect"]["host"] = host or localhost if "port" not in body["connect"]: if port is None: - _, port = listener.getsockname()[:2] + _, port = sockets.get_address(listener) body["connect"]["port"] = port if self.capabilities["supportsStartDebuggingRequest"]: @@ -782,7 +782,7 @@ def serve(host, port): global listener listener = sockets.serve("Client", Client, host, port) sessions.report_sockets() - return listener.getsockname()[:2] + return sockets.get_address(listener) def stop_serving(): diff --git a/src/debugpy/adapter/launchers.py b/src/debugpy/adapter/launchers.py index 6361ff8aa..62f948817 100644 --- a/src/debugpy/adapter/launchers.py +++ b/src/debugpy/adapter/launchers.py @@ -89,7 +89,7 @@ def spawn_debuggee( arguments = dict(start_request.arguments) if not session.no_debug: - _, arguments["port"] = servers.listener.getsockname()[:2] + _, arguments["port"] = sockets.get_address(servers.listener) arguments["adapterAccessToken"] = adapter.access_token def on_launcher_connected(sock): @@ -108,7 +108,7 @@ def on_launcher_connected(sock): sessions.report_sockets() try: - launcher_host, launcher_port = listener.getsockname()[:2] + launcher_host, launcher_port = sockets.get_address(listener) localhost = sockets.get_default_localhost() launcher_addr = ( launcher_port diff --git a/src/debugpy/adapter/servers.py b/src/debugpy/adapter/servers.py index aa31a332c..0aede938f 100644 --- a/src/debugpy/adapter/servers.py +++ b/src/debugpy/adapter/servers.py @@ -395,7 +395,7 @@ def serve(host="127.0.0.1", port=0): global listener listener = sockets.serve("Server", Connection, host, port) sessions.report_sockets() - return listener.getsockname()[:2] + return sockets.get_address(listener) def is_serving(): @@ -475,7 +475,7 @@ def dont_wait_for_first_connection(): def inject(pid, debugpy_args, on_output): - host, port = listener.getsockname()[:2] + host, port = sockets.get_address(listener) cmdline = [ sys.executable, diff --git a/src/debugpy/common/sockets.py b/src/debugpy/common/sockets.py index 0b1c8fadc..47db4d89b 100644 --- a/src/debugpy/common/sockets.py +++ b/src/debugpy/common/sockets.py @@ -49,6 +49,15 @@ def get_default_localhost(): # (this is a very unusual situation) return "127.0.0.1" +def get_address(sock): + """Gets the socket address host and port.""" + try: + host, port = sock.getsockname()[:2] + except Exception as exc: + log.swallow_exception("Failed to get socket address:") + raise RuntimeError(f"Failed to get socket address: {exc}") from exc + + return host, port def create_server(host, port=0, backlog=socket.SOMAXCONN, timeout=None): """Return a local server socket listening on the given port.""" @@ -144,7 +153,7 @@ def serve(name, handler, host, port=0, backlog=socket.SOMAXCONN, timeout=None): log.reraise_exception( "Error listening for incoming {0} connections on {1}:{2}:", name, host, port ) - host, port = listener.getsockname()[:2] + host, port = get_address(listener) log.info("Listening for incoming {0} connections on {1}:{2}...", name, host, port) def accept_worker(): diff --git a/src/debugpy/server/api.py b/src/debugpy/server/api.py index fb76641bd..eb54ffed8 100644 --- a/src/debugpy/server/api.py +++ b/src/debugpy/server/api.py @@ -169,7 +169,7 @@ def listen(address, settrace_kwargs, in_process_debug_adapter=False): raise RuntimeError("can't listen for adapter endpoints: " + str(exc)) try: - endpoints_host, endpoints_port = endpoints_listener.getsockname()[:2] + endpoints_host, endpoints_port = sockets.get_address(endpoints_listener) log.info( "Waiting for adapter endpoints on {0}:{1}...", endpoints_host, diff --git a/tests/debug/comms.py b/tests/debug/comms.py index 0ffb3c5a3..a46ad5ca5 100644 --- a/tests/debug/comms.py +++ b/tests/debug/comms.py @@ -25,7 +25,7 @@ def __str__(self): def listen(self): self._server_socket = sockets.create_server("127.0.0.1", 0, self.TIMEOUT) - _, self.port = self._server_socket.getsockname()[:2] + _, self.port = sockets.get_address(self._server_socket) self._server_socket.listen(0) def accept_worker():