Skip to content

Commit

Permalink
Add ipv6 support for dogstatsd (#791)
Browse files Browse the repository at this point in the history
* Add ipv6 support

* Fix comment

* Use positional arguments for getaddrinfo

Keyword arguments not supported on py2.7

* Restore order of timeout and connect

This makes no difference with udp sockets, but do it anyway for
symmetry and ease of maintenace.
  • Loading branch information
vickenty committed Sep 5, 2023
1 parent 52a23f8 commit cb538b7
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 7 deletions.
31 changes: 25 additions & 6 deletions datadog/dogstatsd/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,12 +625,31 @@ def _get_uds_socket(cls, socket_path, timeout):

@classmethod
def _get_udp_socket(cls, host, port, timeout):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(timeout)
cls._ensure_min_send_buffer_size(sock)
sock.connect((host, port))

return sock
log.debug("Connecting to %s:%s", host, port)
addrinfo = socket.getaddrinfo(host, port, 0, socket.SOCK_DGRAM)
# Override gai.conf order for backwrads compatibility: prefer
# v4, so that a v4-only service on hosts with both addresses
# still works.
addrinfo.sort(key=lambda v: v[0] == socket.AF_INET, reverse=True)
lastaddr = len(addrinfo) - 1
for i, (af, ty, proto, _, addr) in enumerate(addrinfo):
sock = None
try:
sock = socket.socket(af, ty, proto)
sock.settimeout(timeout)
cls._ensure_min_send_buffer_size(sock)
sock.connect(addr)
log.debug("Connected to: %s", addr)
return sock
except Exception as e:
if sock is not None:
sock.close()
log.debug("Failed to connect to %s: %s", addr, e)
if i < lastaddr:
continue
raise e
else:
raise ValueError("getaddrinfo returned no addresses to connect to")

def open_buffer(self, max_buffer_size=None):
"""
Expand Down
31 changes: 30 additions & 1 deletion tests/unit/dogstatsd/test_statsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ def test_udp_socket_ensures_min_receive_buffer(self, mock_socket_create):
datadog.flush()

# Sanity check
mock_socket_create.assert_called_once_with(socket.AF_INET, socket.SOCK_DGRAM)
mock_socket_create.assert_called_once_with(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)

mock_socket.setsockopt.assert_called_once_with(
socket.SOL_SOCKET,
Expand Down Expand Up @@ -1399,6 +1399,35 @@ def wait_for_data():
expected_telemetry = telemetry_metrics(metrics=1, packets_sent=1, bytes_sent=8)
self.assertEqual(udp_thread_telemetry_data, expected_telemetry)

def test_dedicated_udp6_telemetry_dest(self):
listener_sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
listener_sock.bind(('localhost', 0))

def wait_for_data():
global udp_thread_telemetry_data
udp_thread_telemetry_data = listener_sock.recvfrom(UDP_OPTIMAL_PAYLOAD_LENGTH)[0].decode('utf-8')

with closing(listener_sock):
port = listener_sock.getsockname()[1]

dogstatsd = DogStatsd(
host="localhost",
port=12345,
telemetry_min_flush_interval=0,
telemetry_host="::1", # use explicit address, localhost may resolve to v4.
telemetry_port=port,
)

server = threading.Thread(target=wait_for_data)
server.start()

dogstatsd.increment('abc')

server.join(3)

expected_telemetry = telemetry_metrics(metrics=1, packets_sent=1, bytes_sent=8)
self.assertEqual(udp_thread_telemetry_data, expected_telemetry)

def test_dedicated_uds_telemetry_dest(self):
tempdir = tempfile.mkdtemp()
socket_path = os.path.join(tempdir, 'socket.sock')
Expand Down

0 comments on commit cb538b7

Please sign in to comment.