-
Notifications
You must be signed in to change notification settings - Fork 6.8k
[4/n] IPv6 support: Add IPv6 support for sockets #56147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
af1cc3e
7be19c0
f0472e2
ec48d95
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,13 @@ | ||
from typing import Optional, Tuple, Union | ||
import socket | ||
from functools import lru_cache | ||
|
||
from ray._raylet import build_address as _build_address | ||
from ray._raylet import parse_address as _parse_address | ||
from ray._raylet import ( | ||
build_address as _build_address, | ||
is_ipv6_ip as _is_ipv6_ip, | ||
node_ip_address_from_perspective as _node_ip_address_from_perspective, | ||
parse_address as _parse_address, | ||
) | ||
|
||
|
||
def parse_address(address: str) -> Optional[Tuple[str, str]]: | ||
|
@@ -29,6 +35,56 @@ def build_address(host: str, port: Union[int, str]) -> str: | |
return _build_address(host, port) | ||
|
||
|
||
def node_ip_address_from_perspective(address: str = "") -> 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. For | ||
performance, the result is cached when using the default address (empty string). | ||
When a specific address is provided, detection is performed fresh every time. | ||
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_ip(ip: str) -> bool: | ||
"""Check if an IP string is IPv6 format. | ||
Args: | ||
ip: The IP address string to check (must be pure IP, no port). | ||
Returns: | ||
True if the IP is IPv6, False if IPv4. | ||
""" | ||
return _is_ipv6_ip(ip) | ||
|
||
|
||
@lru_cache(maxsize=1) | ||
def get_localhost_address() -> str: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally, |
||
"""Get localhost loopback address with IPv4/IPv6 support. | ||
Returns: | ||
The localhost loopback IP address. | ||
""" | ||
# 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 socket.gaierror: | ||
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. | ||
|
@@ -39,3 +95,28 @@ def is_localhost(host: str) -> bool: | |
True if the host is a localhost address, False otherwise. | ||
""" | ||
return host in ("localhost", "127.0.0.1", "::1") | ||
|
||
|
||
def create_socket(socket_type: int = socket.SOCK_STREAM) -> socket.socket: | ||
"""Create a Python socket object with the appropriate family based on the node IP. | ||
This function automatically gets the node IP address and creates a socket | ||
with the correct family (AF_INET for IPv4, AF_INET6 for IPv6). | ||
Args: | ||
socket_type: The socket type (socket.SOCK_STREAM, socket.SOCK_DGRAM, etc.). | ||
Returns: | ||
A Python socket.socket object configured for the node's IP family. | ||
Example: | ||
# Create a TCP socket for the current node | ||
sock = create_socket() | ||
# Create a UDP socket for the current node | ||
sock = create_socket(socket.SOCK_DGRAM) | ||
""" | ||
node_ip = node_ip_address_from_perspective() | ||
family = socket.AF_INET6 if is_ipv6_ip(node_ip) else socket.AF_INET | ||
|
||
return socket.socket(family, socket_type) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,7 +22,12 @@ | |
import ray | ||
import ray._private.ray_constants as ray_constants | ||
import ray._private.services | ||
from ray._common.network_utils import build_address, parse_address | ||
from ray._common.network_utils import ( | ||
build_address, | ||
create_socket, | ||
get_localhost_address, | ||
parse_address, | ||
) | ||
from ray._common.ray_constants import LOGGING_ROTATE_BACKUP_COUNT, LOGGING_ROTATE_BYTES | ||
from ray._common.utils import try_to_create_directory | ||
from ray._private.resource_and_label_spec import ResourceAndLabelSpec | ||
|
@@ -138,7 +143,7 @@ def __init__( | |
) | ||
|
||
self._resource_and_label_spec = None | ||
self._localhost = socket.gethostbyname("localhost") | ||
self._localhost = get_localhost_address() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The old way should just work for both ipv4 and ipv6 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I checked the documentation here, it seems that it does not support it: https://docs.python.org/3/library/socket.html There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the new function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer to have a consistent way to determine whether to use IPv6 or IPv4. The current approach is: |
||
self._ray_params = ray_params | ||
self._config = ray_params._system_config or {} | ||
|
||
|
@@ -880,7 +885,7 @@ def _get_unused_port(self, allocated_ports=None): | |
if allocated_ports is None: | ||
allocated_ports = set() | ||
|
||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
s = create_socket(socket.SOCK_STREAM) | ||
s.bind(("", 0)) | ||
port = s.getsockname()[1] | ||
|
||
|
@@ -893,7 +898,7 @@ def _get_unused_port(self, allocated_ports=None): | |
# This port is allocated for other usage already, | ||
# so we shouldn't use it even if it's not in use right now. | ||
continue | ||
new_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
new_s = create_socket(socket.SOCK_STREAM) | ||
try: | ||
new_s.bind(("", new_port)) | ||
except OSError: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed the function to GetNodeIpAddressFromPerspective to keep it consistent with the Python function node_ip_address_from_perspective. Additionally, I used CPython bindings to avoid maintaining duplicate implementations in both C++ and Python.