|
| 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