Skip to content

Commit 27557f0

Browse files
committed
docs: [#220] add ADR for port zero not supported in bind addresses
1 parent 5cc10f5 commit 27557f0

File tree

1 file changed

+195
-0
lines changed

1 file changed

+195
-0
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# Port Zero Not Supported in Bind Addresses
2+
3+
**Status**: Accepted
4+
5+
**Date**: December 11, 2025
6+
7+
**Author**: Development Team
8+
9+
**Related Issues**: [#220]
10+
11+
---
12+
13+
## Context
14+
15+
The Torrust Tracker Deployer allows users to configure tracker services with bind addresses (e.g., `0.0.0.0:6969` for UDP tracker, `0.0.0.0:7070` for HTTP tracker). These bind addresses are used throughout the deployment lifecycle:
16+
17+
1. **Environment Creation (`create` command)**: Configuration is validated and stored
18+
2. **Configuration (`configure` command)**: Firewall rules are established based on specified ports
19+
3. **Software Release (`release` command)**: Tracker is configured with bind addresses
20+
4. **Service Execution (`run` command)**: Tracker services are started with configured ports
21+
22+
### The Port Zero Problem
23+
24+
Port `0` is a special value in network programming that means "let the operating system assign any available ephemeral port dynamically." While this is useful for applications where the specific port doesn't matter, it creates significant challenges in our deployment workflow:
25+
26+
**Firewall Configuration Conflict**: The `configure` command must open specific firewall ports **before** the tracker starts. With port `0`, we don't know which port the OS will assign until the tracker actually starts, creating a chicken-and-egg problem:
27+
28+
- We can't configure the firewall without knowing the port
29+
- We can't start the tracker without opening the firewall
30+
- We can't know the port without starting the tracker
31+
32+
**User Expectations**: Users specify bind addresses expecting those exact ports to be used consistently across:
33+
34+
- Firewall rules (UFW configuration)
35+
- Service configuration (tracker TOML files)
36+
- Health checks (validation commands)
37+
- External access (port forwarding, client connections)
38+
39+
Dynamic port assignment would break this expectation and make the system unpredictable.
40+
41+
## Decision
42+
43+
We **explicitly reject port 0** in all tracker bind address configurations. This validation occurs at the **DTO-to-Domain boundary** when converting `TrackerSection` (application layer DTO) to `TrackerConfig` (domain type).
44+
45+
### Implementation Location
46+
47+
Validation is performed in the conversion methods of each tracker section:
48+
49+
- `UdpTrackerSection::to_udp_tracker_config()`
50+
- `HttpTrackerSection::to_http_tracker_config()`
51+
- `HttpApiSection::to_http_api_config()`
52+
53+
### Error Handling
54+
55+
When port 0 is detected, we return a clear, actionable error:
56+
57+
```rust
58+
CreateConfigError::DynamicPortNotSupported {
59+
bind_address: "0.0.0.0:0".to_string(),
60+
}
61+
```
62+
63+
The error message includes:
64+
65+
- What went wrong (dynamic port assignment not supported)
66+
- Why it's not supported (conflicts with firewall configuration)
67+
- How to fix it (specify an explicit port number)
68+
69+
## Consequences
70+
71+
### Positive
72+
73+
1. **Predictable Deployment**: Users know exactly which ports will be used
74+
2. **Consistent Configuration**: Same ports across all deployment phases
75+
3. **Firewall Compatibility**: Can configure firewall rules before service starts
76+
4. **Clear Documentation**: Users understand port requirements upfront
77+
5. **Fail Fast**: Errors appear at environment creation, not during service startup
78+
79+
### Negative
80+
81+
1. **Port Conflicts**: Users must manually choose available ports
82+
2. **Multi-Instance Deployments**: Each instance needs unique ports
83+
84+
### Neutral
85+
86+
1. **Validation Overhead**: Minimal - single integer comparison per bind address
87+
2. **Test Coverage**: Requires additional test cases for port 0 rejection
88+
89+
## Alternatives Considered
90+
91+
### Alternative 1: Support Dynamic Ports with Runtime Discovery
92+
93+
**Approach**: Allow port 0, then discover the assigned port after service starts.
94+
95+
**How It Would Work**:
96+
97+
1. User specifies port 0 in configuration
98+
2. Tracker starts and OS assigns ephemeral port
99+
3. Parse Docker container logs or query Docker port mappings
100+
4. Extract dynamically assigned port
101+
5. Update firewall rules with discovered port
102+
103+
**Rejected Because**:
104+
105+
- Adds significant complexity to the deployment workflow
106+
- Creates timing dependencies (must wait for service to start before configuring firewall)
107+
- Breaks the "configure before deploy" model
108+
- Requires Docker-specific inspection logic
109+
- Makes health checks and validation more complex
110+
- Could be revisited in future if there's strong user demand
111+
112+
### Alternative 2: Auto-Assign Sequential Ports
113+
114+
**Approach**: If port 0 is specified, automatically assign the next available port from a predefined range.
115+
116+
**Rejected Because**:
117+
118+
- Requires port availability checking across potentially remote systems
119+
- Introduces race conditions in multi-deployment scenarios
120+
- Hides port selection from users, reducing transparency
121+
- Adds complexity without clear benefits
122+
123+
### Alternative 3: Port Range Specification
124+
125+
**Approach**: Allow users to specify port ranges (e.g., `6969-6979`) and pick the first available.
126+
127+
**Rejected Because**:
128+
129+
- More complex than current single-port model
130+
- Still requires availability checking
131+
- Doesn't solve the fundamental firewall configuration problem
132+
- Adds unnecessary flexibility for most use cases
133+
134+
## Implementation Notes
135+
136+
### Where Validation Happens
137+
138+
```text
139+
JSON Configuration (String)
140+
141+
TrackerSection (DTO with String bind_address)
142+
143+
[VALIDATION POINT - Reject port 0]
144+
145+
TrackerConfig (Domain with SocketAddr bind_address)
146+
```
147+
148+
### Example Error Output
149+
150+
```text
151+
Error: Dynamic port assignment (port 0) is not supported in bind address '0.0.0.0:0'
152+
153+
Why: Port 0 tells the OS to assign any available port dynamically. This conflicts
154+
with our firewall configuration which needs to know exact ports before services start.
155+
156+
Solution: Specify an explicit port number in your configuration:
157+
- UDP Tracker: Use a port like 6969 (default)
158+
- HTTP Tracker: Use a port like 7070 (default)
159+
- HTTP API: Use a port like 1212 (default)
160+
161+
Example:
162+
"udp_trackers": [
163+
{ "bind_address": "0.0.0.0:6969" } ← Explicit port, not 0
164+
]
165+
```
166+
167+
## Future Considerations
168+
169+
If there's strong user demand for dynamic port assignment:
170+
171+
1. Could implement runtime port discovery as an optional feature
172+
2. Would require:
173+
- Docker port mapping inspection
174+
- Delayed firewall configuration
175+
- Updated health check logic
176+
- Clear documentation of limitations
177+
3. Would be a **separate feature**, not a change to current behavior
178+
179+
For now, the explicit port requirement provides the best balance of:
180+
181+
- Simplicity
182+
- Predictability
183+
- Compatibility with existing deployment workflow
184+
185+
## References
186+
187+
- [Issue #220]: Tracker Slice - Release and Run Commands
188+
- `docs/implementation-plans/issue-220-test-command-architecture.md`: Implementation plan
189+
- `docs/contributing/error-handling.md`: Error handling principles
190+
- [UFW Documentation](https://help.ubuntu.com/community/UFW): Firewall configuration
191+
192+
---
193+
194+
**Decision Made**: December 11, 2025
195+
**Last Updated**: December 11, 2025

0 commit comments

Comments
 (0)