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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## Unreleased

***Fixed:***

- Fix the allowed range of exposed developer environment ports

## 0.33.1 - 2026-03-27

***Added:***
Expand Down
8 changes: 4 additions & 4 deletions src/dda/env/dev/types/linux_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,15 +358,15 @@ def hostname(self) -> str:

@cached_property
def ssh_port(self) -> int:
from dda.utils.network.protocols import derive_dynamic_port
from dda.utils.network.protocols import derive_service_port

return derive_dynamic_port(f"{self.container_name}-ssh")
return derive_service_port(f"{self.container_name}-ssh")

@cached_property
def mcp_port(self) -> int:
from dda.utils.network.protocols import derive_dynamic_port
from dda.utils.network.protocols import derive_service_port

return derive_dynamic_port(f"{self.container_name}-mcp")
return derive_service_port(f"{self.container_name}-mcp")

@cached_property
def home_dir(self) -> str:
Expand Down
25 changes: 17 additions & 8 deletions src/dda/utils/network/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,24 @@
# SPDX-License-Identifier: MIT
from __future__ import annotations

# Port classes per RFC 6335 §6:
# - System/Well-Known: 0-1023
# - User/Registered: 1024-49151
# - Dynamic/Private/Ephemeral: 49152-65535
#
# We choose a subrange of the User range and keep it:
# - >= 20000 to reduce collisions with commonly used low-numbered ports in practice
# - < 32768 to stay below Linux's default ephemeral start (net.ipv4.ip_local_port_range)
MIN_SERVICE_PORT = 20000
MAX_SERVICE_PORT = 32767
SERVICE_PORT_RANGE = MAX_SERVICE_PORT - MIN_SERVICE_PORT + 1

def derive_dynamic_port(key: str) -> int:
from hashlib import sha256

# https://en.wikipedia.org/wiki/Ephemeral_port
# https://datatracker.ietf.org/doc/html/rfc6335#section-6
# https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
min_port = 49152
max_port = 65535
def derive_service_port(key: str) -> int:
"""
Deterministically map `key` to a TCP/UDP port.
"""
from hashlib import sha256

key_hash = int.from_bytes(sha256(key.encode("utf-8")).digest(), "big")
return key_hash % (max_port - min_port) + min_port
return key_hash % SERVICE_PORT_RANGE + MIN_SERVICE_PORT
44 changes: 22 additions & 22 deletions tests/env/dev/types/test_linux_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,9 @@ def test_default(self, dda, helpers, mocker, temp_dir, host_user_args):
"--name",
"dda-linux-container-default",
"-p",
"61938:22",
"26090:22",
"-p",
"50069:9000",
"31381:9000",
"-v",
"/var/run/docker.sock:/var/run/docker.sock",
*host_user_args,
Expand Down Expand Up @@ -299,9 +299,9 @@ def test_clone(self, dda, helpers, mocker, temp_dir, host_user_args):
"--name",
"dda-linux-container-default",
"-p",
"61938:22",
"26090:22",
"-p",
"50069:9000",
"31381:9000",
"-v",
"/var/run/docker.sock:/var/run/docker.sock",
*host_user_args,
Expand Down Expand Up @@ -334,7 +334,7 @@ def test_clone(self, dda, helpers, mocker, temp_dir, host_user_args):
"-q",
"-t",
"-p",
"61938",
"26090",
"root@localhost",
"--",
"cd /root && git dd-clone datadog-agent",
Expand Down Expand Up @@ -396,9 +396,9 @@ def test_no_pull(self, dda, helpers, mocker, temp_dir, host_user_args):
"--name",
"dda-linux-container-default",
"-p",
"61938:22",
"26090:22",
"-p",
"50069:9000",
"31381:9000",
"-v",
"/var/run/docker.sock:/var/run/docker.sock",
*host_user_args,
Expand Down Expand Up @@ -487,9 +487,9 @@ def test_multiple(self, dda, helpers, mocker, temp_dir, host_user_args):
"--name",
"dda-linux-container-default",
"-p",
"61938:22",
"26090:22",
"-p",
"50069:9000",
"31381:9000",
"-v",
"/var/run/docker.sock:/var/run/docker.sock",
*host_user_args,
Expand Down Expand Up @@ -573,9 +573,9 @@ def test_multiple_clones(self, dda, helpers, mocker, temp_dir, host_user_args):
"--name",
"dda-linux-container-default",
"-p",
"61938:22",
"26090:22",
"-p",
"50069:9000",
"31381:9000",
"-v",
"/var/run/docker.sock:/var/run/docker.sock",
*host_user_args,
Expand Down Expand Up @@ -608,7 +608,7 @@ def test_multiple_clones(self, dda, helpers, mocker, temp_dir, host_user_args):
"-q",
"-t",
"-p",
"61938",
"26090",
"root@localhost",
"--",
"cd /root && git dd-clone datadog-agent tag",
Expand All @@ -624,7 +624,7 @@ def test_multiple_clones(self, dda, helpers, mocker, temp_dir, host_user_args):
"-q",
"-t",
"-p",
"61938",
"26090",
"root@localhost",
"--",
"cd /root && git dd-clone integrations-core",
Expand Down Expand Up @@ -694,9 +694,9 @@ def test_extra_volume_specs(self, dda, helpers, mocker, temp_dir, host_user_args
"--name",
"dda-linux-container-default",
"-p",
"61938:22",
"26090:22",
"-p",
"50069:9000",
"31381:9000",
"-v",
"/var/run/docker.sock:/var/run/docker.sock",
*host_user_args,
Expand Down Expand Up @@ -799,9 +799,9 @@ def test_extra_mounts(self, dda, helpers, mocker, temp_dir, host_user_args, moun
"--name",
"dda-linux-container-default",
"-p",
"61938:22",
"26090:22",
"-p",
"50069:9000",
"31381:9000",
"-v",
"/var/run/docker.sock:/var/run/docker.sock",
*host_user_args,
Expand Down Expand Up @@ -942,7 +942,7 @@ def test_default(self, dda, helpers, mocker):
"-q",
"-t",
"-p",
"61938",
"26090",
"root@localhost",
"--",
"cd /root/repos/datadog-agent && zsh -l -i",
Expand Down Expand Up @@ -990,7 +990,7 @@ def test_default(self, dda, helpers, mocker):
"-q",
"-t",
"-p",
"61938",
"26090",
"root@localhost",
"--",
"cd /root/repos/datadog-agent && echo foo",
Expand Down Expand Up @@ -1040,7 +1040,7 @@ def test_default(self, dda, helpers, mocker):
[
"code",
"--remote",
"ssh-remote+root@localhost:61938",
"ssh-remote+root@localhost:26090",
"/root/repos/datadog-agent",
],
)
Expand Down Expand Up @@ -1073,7 +1073,7 @@ def test_editor_flag(self, dda, helpers, mocker):
[
"cursor",
"--remote",
"ssh-remote+root@localhost:61938",
"ssh-remote+root@localhost:26090",
"/root/repos/datadog-agent",
],
)
Expand Down Expand Up @@ -1109,7 +1109,7 @@ def test_editor_config(self, dda, config_file, helpers, mocker):
[
"cursor",
"--remote",
"ssh-remote+root@localhost:61938",
"ssh-remote+root@localhost:26090",
"/root/repos/datadog-agent",
],
)
Expand Down
6 changes: 3 additions & 3 deletions tests/utils/network/test_protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import hypothesis
from hypothesis import strategies as st

from dda.utils.network.protocols import derive_dynamic_port
from dda.utils.network.protocols import derive_service_port


@hypothesis.given(st.text())
def test_derive_dynamic_port(key: str) -> None:
assert 49152 <= derive_dynamic_port(key) <= 65535
def test_derive_service_port(key: str) -> None:
assert 20000 <= derive_service_port(key) <= 32767
Loading