Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 7 additions & 80 deletions python/ray/_common/network_utils.py
Original file line number Diff line number Diff line change
@@ -1,89 +1,16 @@
import socket
from contextlib import closing
from functools import lru_cache
from typing import Optional, Tuple, Union

from ray._raylet import (
build_address as _build_address,
is_ipv6 as _is_ipv6,
node_ip_address_from_perspective as _node_ip_address_from_perspective,
parse_address as _parse_address,
from ray._raylet import ( # noqa: F401
build_address,
get_all_interfaces_ip,
get_localhost_ip,
is_ipv6,
node_ip_address_from_perspective,
parse_address,
)


def parse_address(address: str) -> Optional[Tuple[str, str]]:
"""Parse a network address string into host and port.

Args:
address: The address string to parse (e.g., "localhost:8000", "[::1]:8000").

Returns:
Tuple with (host, port) if port found, None if no colon separator.
"""
return _parse_address(address)


def build_address(host: str, port: Union[int, str]) -> str:
"""Build a network address string from host and port.

Args:
host: The hostname or IP address.
port: The port number (int or string).

Returns:
Formatted address string (e.g., "localhost:8000" or "[::1]:8000").
"""
return _build_address(host, port)


def node_ip_address_from_perspective(address: Optional[str] = None) -> str:
"""IP address by which the local node can be reached *from* the `address`.

If no address is given, defaults to public DNS servers for detection.

Args:
address: The IP address and port of any known live service on the
network you care about.

Returns:
The IP address by which the local node can be reached from the address.
"""
return _node_ip_address_from_perspective(address)


def is_ipv6(host: str) -> bool:
"""Check if a host is resolved to IPv6.

Args:
host: The IP or domain name to check (must be without port).

Returns:
True if the host is resolved to IPv6, False if IPv4.
"""
return _is_ipv6(host)


@lru_cache(maxsize=1)
def get_localhost_ip() -> str:
"""Get localhost loopback ip with IPv4/IPv6 support.

Returns:
The localhost loopback IP.
"""
# Try IPv4 first, then IPv6 localhost resolution
for family in [socket.AF_INET, socket.AF_INET6]:
try:
dns_result = socket.getaddrinfo(
"localhost", None, family, socket.SOCK_STREAM
)
return dns_result[0][4][0]
except Exception:
continue

# Final fallback to IPv4 loopback
return "127.0.0.1"


def is_localhost(host: str) -> bool:
"""Check if the given host string represents a localhost address.

Expand Down
10 changes: 5 additions & 5 deletions python/ray/_private/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Dict, List, Optional

import ray._private.ray_constants as ray_constants
from ray._common.network_utils import get_localhost_ip
from ray._private.resource_isolation_config import ResourceIsolationConfig
from ray._private.utils import get_ray_client_dependency_error

Expand Down Expand Up @@ -75,10 +76,9 @@ class RayParams:
UI, which displays the status of the Ray cluster. If this value is
None, then the UI will be started if the relevant dependencies are
present.
dashboard_host: The host to bind the web UI server to. Can either be
localhost (127.0.0.1) or 0.0.0.0 (available from all interfaces).
By default, this is set to localhost to prevent access from
external machines.
dashboard_host: The host to bind the dashboard server to. Use localhost
(127.0.0.1/::1) for local access, the node IP for remote access, or
0.0.0.0/:: for all interfaces (not recommended). Defaults to localhost.
dashboard_port: The port to bind the dashboard server to.
Defaults to 8265.
dashboard_agent_listen_port: The port for dashboard agents to listen on
Expand Down Expand Up @@ -154,7 +154,7 @@ def __init__(
setup_worker_path: Optional[str] = None,
huge_pages: Optional[bool] = False,
include_dashboard: Optional[bool] = None,
dashboard_host: Optional[str] = ray_constants.DEFAULT_DASHBOARD_IP,
dashboard_host: Optional[str] = get_localhost_ip(),
dashboard_port: Optional[bool] = ray_constants.DEFAULT_DASHBOARD_PORT,
dashboard_agent_listen_port: Optional[
int
Expand Down
1 change: 0 additions & 1 deletion python/ray/_private/ray_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ def env_set_by_user(key):
# Timeout waiting for the dashboard to come alive during node startup.
RAY_DASHBOARD_STARTUP_TIMEOUT_S = env_integer("RAY_DASHBOARD_STARTUP_TIMEOUT_S", 60)

DEFAULT_DASHBOARD_IP = "127.0.0.1"
DEFAULT_DASHBOARD_PORT = 8265
DASHBOARD_ADDRESS = "dashboard"
DASHBOARD_CLIENT_MAX_SIZE = 100 * 1024**2
Expand Down
11 changes: 6 additions & 5 deletions python/ray/_private/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
)
from urllib.parse import urlparse

from ray._common.network_utils import get_localhost_ip

if TYPE_CHECKING:
import torch

Expand Down Expand Up @@ -1420,7 +1422,7 @@ def init(
local_mode: bool = False,
ignore_reinit_error: bool = False,
include_dashboard: Optional[bool] = None,
dashboard_host: str = ray_constants.DEFAULT_DASHBOARD_IP,
dashboard_host: str = get_localhost_ip(),
dashboard_port: Optional[int] = None,
job_config: "ray.job_config.JobConfig" = None,
configure_logging: bool = True,
Expand Down Expand Up @@ -1513,10 +1515,9 @@ def init(
Ray dashboard, which displays the status of the Ray
cluster. If this argument is None, then the UI will be started if
the relevant dependencies are present.
dashboard_host: The host to bind the dashboard server to. Can either be
localhost (127.0.0.1) or 0.0.0.0 (available from all interfaces).
By default, this is set to localhost to prevent access from
external machines.
dashboard_host: The host to bind the dashboard server to. Use localhost
(127.0.0.1/::1) for local access, the node IP for remote access, or
0.0.0.0/:: for all interfaces (not recommended). Defaults to localhost.
dashboard_port(int, None): The port to bind the dashboard server to.
Defaults to 8265 and Ray will automatically find a free port if
8265 is not available.
Expand Down
11 changes: 9 additions & 2 deletions python/ray/autoscaler/_private/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@

import ray
import ray._private.ray_constants as ray_constants
from ray._common.network_utils import build_address, parse_address
from ray._common.network_utils import (
build_address,
get_localhost_ip,
is_localhost,
parse_address,
)
from ray._common.ray_constants import (
LOGGING_ROTATE_BACKUP_COUNT,
LOGGING_ROTATE_BYTES,
Expand Down Expand Up @@ -195,7 +200,9 @@ def __init__(
AUTOSCALER_METRIC_PORT
)
)
kwargs = {"addr": "127.0.0.1"} if head_node_ip == "127.0.0.1" else {}
kwargs = (
{"addr": get_localhost_ip()} if is_localhost(head_node_ip) else {}
)
prometheus_client.start_http_server(
port=AUTOSCALER_METRIC_PORT,
registry=self.prom_metrics.registry,
Expand Down
11 changes: 9 additions & 2 deletions python/ray/autoscaler/v2/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@

import ray
import ray._private.ray_constants as ray_constants
from ray._common.network_utils import build_address, parse_address
from ray._common.network_utils import (
build_address,
get_localhost_ip,
is_localhost,
parse_address,
)
from ray._common.ray_constants import (
LOGGING_ROTATE_BACKUP_COUNT,
LOGGING_ROTATE_BYTES,
Expand Down Expand Up @@ -114,7 +119,9 @@ def __init__(
AUTOSCALER_METRIC_PORT
)
)
kwargs = {"addr": "127.0.0.1"} if head_node_ip == "127.0.0.1" else {}
kwargs = (
{"addr": get_localhost_ip()} if is_localhost(head_node_ip) else {}
)
prometheus_client.start_http_server(
port=AUTOSCALER_METRIC_PORT,
registry=prom_metrics.registry,
Expand Down
14 changes: 5 additions & 9 deletions python/ray/dashboard/head.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import ray.dashboard.consts as dashboard_consts
import ray.dashboard.utils as dashboard_utils
import ray.experimental.internal_kv as internal_kv
from ray._common.network_utils import build_address
from ray._common.network_utils import build_address, get_localhost_ip, is_localhost
from ray._common.usage.usage_lib import TagKey, record_extra_usage_tag
from ray._private import ray_constants
from ray._private.async_utils import enable_monitor_loop_lag
Expand Down Expand Up @@ -96,7 +96,7 @@ def __init__(
self.serve_frontend = False
# Public attributes are accessible for all head modules.
# Walkaround for issue: https://github.com/ray-project/ray/issues/7084
self.http_host = "127.0.0.1" if http_host == "localhost" else http_host
self.http_host = get_localhost_ip() if http_host == "localhost" else http_host
self.http_port = http_port
self.http_port_retries = http_port_retries
self._modules_to_load = modules_to_load
Expand Down Expand Up @@ -313,7 +313,7 @@ async def _setup_metrics(self, gcs_client):
DASHBOARD_METRIC_PORT
)
)
kwargs = {"addr": "127.0.0.1"} if self.ip == "127.0.0.1" else {}
kwargs = {"addr": get_localhost_ip()} if is_localhost(self.ip) else {}
prometheus_client.start_http_server(
port=DASHBOARD_METRIC_PORT,
registry=metrics.registry,
Expand Down Expand Up @@ -444,12 +444,8 @@ def on_new_lag(lag_s):
logger.info("http server disabled.")

# We need to expose dashboard's node's ip for other worker nodes
# if it's listening to all interfaces.
dashboard_http_host = (
self.ip
if self.http_host != ray_constants.DEFAULT_DASHBOARD_IP
else http_host
)
# if it's not localhost.
dashboard_http_host = self.ip if not is_localhost(self.http_host) else http_host
# This synchronous code inside an async context is not great.
# It is however acceptable, because this only gets run once
# during initialization and therefore cannot block the event loop.
Expand Down
4 changes: 2 additions & 2 deletions python/ray/dashboard/http_server_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from packaging.version import Version

import ray.dashboard.optional_utils as dashboard_optional_utils
from ray._common.network_utils import build_address, is_localhost
from ray._common.network_utils import build_address, get_localhost_ip, is_localhost
from ray._common.utils import get_or_create_event_loop
from ray._private.authentication.http_token_authentication import (
get_token_auth_middleware,
Expand Down Expand Up @@ -60,7 +60,7 @@ async def _start_site_with_retry(
if not is_localhost(self.ip):
local_site = aiohttp.web.TCPSite(
self.runner,
"127.0.0.1",
get_localhost_ip(),
self.listen_port,
)
await local_site.start()
Expand Down
4 changes: 2 additions & 2 deletions python/ray/dashboard/modules/job/tests/test_job_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import pytest

import ray
from ray._common.network_utils import build_address
from ray._common.network_utils import build_address, get_localhost_ip
from ray._common.test_utils import (
FakeTimer,
SignalActor,
Expand Down Expand Up @@ -1370,7 +1370,7 @@ async def test_bootstrap_address(job_manager, monkeypatch):
cluster might be started with http://ip:{dashboard_port} from previous
runs.
"""
ip = ray._private.ray_constants.DEFAULT_DASHBOARD_IP
ip = get_localhost_ip()
port = ray._private.ray_constants.DEFAULT_DASHBOARD_PORT

monkeypatch.setenv("RAY_ADDRESS", f"http://{build_address(ip, port)}")
Expand Down
3 changes: 2 additions & 1 deletion python/ray/dashboard/modules/reporter/reporter_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import ray._private.prometheus_exporter as prometheus_exporter
import ray.dashboard.modules.reporter.reporter_consts as reporter_consts
import ray.dashboard.utils as dashboard_utils
from ray._common.network_utils import get_localhost_ip, is_localhost
from ray._common.utils import (
get_or_create_event_loop,
)
Expand Down Expand Up @@ -452,7 +453,7 @@ def __init__(self, dashboard_agent, raylet_client=None):
prometheus_exporter.Options(
namespace="ray",
port=dashboard_agent.metrics_export_port,
address="127.0.0.1" if self._ip == "127.0.0.1" else "",
address=get_localhost_ip() if is_localhost(self._ip) else "",
)
)
dashboard_agent.metrics_export_port = stats_exporter.port
Expand Down
24 changes: 10 additions & 14 deletions python/ray/dashboard/modules/reporter/tests/test_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1202,16 +1202,13 @@ def get_pid(self):

a = MyActor.remote()
worker_pid = ray.get(a.get_pid.remote())
node = ray._private.worker.global_worker.node
dashboard_agent = MagicMock()
dashboard_agent.gcs_address = build_address("127.0.0.1", 6379)
dashboard_agent.ip = "127.0.0.1"
dashboard_agent.node_manager_port = (
ray._private.worker.global_worker.node.node_manager_port
)
dashboard_agent.session_dir = (
ray._private.worker.global_worker.node.get_session_dir_path()
)
dashboard_agent.node_id = ray._private.worker.global_worker.node.unique_id
dashboard_agent.gcs_address = build_address(node.node_ip_address, 6379)
dashboard_agent.ip = node.node_ip_address
dashboard_agent.node_manager_port = node.node_manager_port
dashboard_agent.session_dir = node.get_session_dir_path()
dashboard_agent.node_id = ray.NodeID.from_random().hex()
agent = ReporterAgent(dashboard_agent)
pids = await agent._async_get_worker_pids_from_raylet()
assert len(pids) == 2
Expand All @@ -1232,14 +1229,13 @@ def get_pid(self):
async def test_reporter_dashboard_and_runtime_env_agent(
ray_start_with_dashboard, tmp_path
):
node = ray._private.worker.global_worker.node
dashboard_agent = MagicMock()
dashboard_agent.gcs_address = build_address("127.0.0.1", 6379)
dashboard_agent.gcs_address = build_address(node.node_ip_address, 6379)
dashboard_agent.session_dir = str(tmp_path)
dashboard_agent.node_id = ray.NodeID.from_random().hex()
dashboard_agent.ip = "127.0.0.1"
dashboard_agent.node_manager_port = (
ray._private.worker.global_worker.node.node_manager_port
)
dashboard_agent.ip = node.node_ip_address
dashboard_agent.node_manager_port = node.node_manager_port
agent = ReporterAgent(dashboard_agent)
agent_pids = await agent._async_get_agent_pids_from_raylet()
assert len(agent_pids) == 2
Expand Down
2 changes: 2 additions & 0 deletions python/ray/includes/network_util.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ cdef extern from "ray/util/network_util.h" namespace "ray":
optional[array_string_2] ParseAddress(const string &address)
string GetNodeIpAddressFromPerspective(const optional[string] &address)
bool IsIPv6(const string &host)
string GetLocalhostIP()
string GetAllInterfacesIP()
Loading