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
1 change: 1 addition & 0 deletions config/tests/deep-segments-config.data.xml
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@
<ref class="Variable" id="local-env-ers-warning"/>
<ref class="Variable" id="local-env-ers-error"/>
<ref class="Variable" id="local-env-ers-fatal"/>
<ref class="Variable" id="local-env-connectivity-port"/>
</rel>
<rel name="segment" class="Segment" id="segment-0"/>
<rel name="infrastructure_applications">
Expand Down
1 change: 1 addition & 0 deletions config/tests/nestedConfig.data.xml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@
<ref class="Variable" id="local-env-ers-warning"/>
<ref class="Variable" id="local-env-ers-error"/>
<ref class="Variable" id="local-env-ers-fatal"/>
<ref class="Variable" id="local-env-connectivity-port"/>
</rel>
<rel name="segment" class="Segment" id="top-segment"/>
<rel name="infrastructure_applications">
Expand Down
1 change: 1 addition & 0 deletions config/tests/one-controller-config.data.xml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
<ref class="Variable" id="local-env-ers-warning"/>
<ref class="Variable" id="local-env-ers-error"/>
<ref class="Variable" id="local-env-ers-fatal"/>
<ref class="Variable" id="local-env-connectivity-port"/>
</rel>
<rel name="segment" class="Segment" id="segment-0"/>
<rel name="infrastructure_applications">
Expand Down
4 changes: 3 additions & 1 deletion src/drunc/controller/controller_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ def __init__(self, address: str, token: Token):
options = [
("grpc.keepalive_time_ms", 60000) # pings the server every 60 seconds
]
Comment thread
MRiganSUSX marked this conversation as resolved.
self.channel = grpc.insecure_channel(self.address, options=options)
# The 'ipv4:' prefix forces IPv4 resolution, which helps avoid Kubernetes hairpinning issues
target_address = f"ipv4:{self.address}"
self.channel = grpc.insecure_channel(target_address, options=options)
self.stub = ControllerStub(self.channel)
self.token = Token()
self.token.CopyFrom(token)
Expand Down
54 changes: 51 additions & 3 deletions src/drunc/controller/interface/shell_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
import ipaddress
import logging
import os
import socket
Expand All @@ -7,7 +8,7 @@
from collections.abc import Sequence
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from functools import partial
from functools import lru_cache, partial
from urllib.parse import urlparse

import click
Expand Down Expand Up @@ -113,8 +114,8 @@ def update_endpoint(endpoint: str) -> str:
ip_address = urlparse(endpoint).hostname
if not ip_address:
return ""
hostname, _, _ = socket.gethostbyaddr(ip_address)
return endpoint.replace(ip_address, hostname)
resolved_host = get_hostname_smart(ip_address)
return endpoint.replace(ip_address, resolved_host)

table.add_row(
prefix + status_response.name,
Expand Down Expand Up @@ -709,3 +710,50 @@ def grab_default_value_from_env(argument_name):
)(cmd)

return cmd, cmd_name


@lru_cache(maxsize=1024)
def is_private_ip(ip_str: str) -> bool:
"""
Checks if an IP address is private (RFC 1918), loopback, or link-local.
These IPs will almost never have a public reverse DNS record.
"""
if not ip_str:
return True
try:
ip_obj = ipaddress.ip_address(ip_str)
# .is_private = 10.x, 172.16-31.x, 192.168.x
# .is_loopback = 127.x.x.x
# .is_link_local = 169.254.x.x
return ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local
except ValueError:
# Not 'valid' IP address -> treat as private
return True


@lru_cache(maxsize=4096)
def get_hostname_smart(ip_address: str, timeout_seconds: float = 0.2) -> str:
"""
Resolves an IP to a hostname, with optimizations:
1. Caches all results.
2. Immediately skips private/internal IPs (like K8s).
3. Uses a short timeout for public IPs.
"""

# If private IP (k8s), don't try to resolve it
if is_private_ip(ip_address):
return ip_address

# If public IP, try to resolve it.
original_timeout = socket.getdefaulttimeout()
try:
socket.setdefaulttimeout(timeout_seconds)

hostname, _, _ = socket.gethostbyaddr(ip_address)
return hostname

except (socket.herror, socket.gaierror, socket.timeout):
return ip_address

finally:
socket.setdefaulttimeout(original_timeout)
4 changes: 3 additions & 1 deletion src/drunc/data/process_manager/k8s.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@
"checking": {
"watcher_retry_sleep": 5,
"pod_status_check_sleep": 1,
"host_cache_expiry": 300
"host_cache_expiry": 300,
"grpc_startup_timeout": 30,
"socket_retry_timeout": 1.0
}
}
}
Loading
Loading