Skip to main content

netget/protocol/
default_port.rs

1//! The port a server starts on when the caller names none.
2//!
3//! One decision, used everywhere a port can be left out: the dashboard's picker and create
4//! form, `ServerForm`, MCP `start_server`, the model's `open_server` and `--server`. All of
5//! them reach it through [`crate::cli::server_startup::start_server_from_action`] or, for the
6//! picker's preview, through [`default_port_for_server`] directly, so the port a preview names
7//! is the port a start takes.
8//!
9//! The rule:
10//!
11//! 1. **An explicit port is never replaced** — `0` included. This module is consulted only
12//!    when the caller passed no port at all.
13//! 2. The protocol's declared [`ProtocolMetadataV2::well_known_port`] when it is 1024 or above,
14//!    or when it is below 1024 and this process can bind privileged ports
15//!    ([`SystemCapabilities::can_bind_privileged_ports`], the existing probe — not a new one).
16//! 3. Otherwise `0`, an OS-assigned port, **with the reason**: the privileged port this
17//!    process cannot bind, or the port something else already holds.
18//!
19//! A port already taken falls back rather than failing, because the caller who named no port
20//! asked for "a" server, not for that number — and the one who needs the number passes it, in
21//! which case a taken port is an error they should see.
22//!
23//! The in-use check binds and drops a probe socket on the address the server would use. That
24//! is the probe-then-bind pattern `server_startup.rs` removed for port 0, and it is safe here
25//! for the reason it was not there: a fixed port cannot be handed to someone else between the
26//! probe and the real bind. If another process takes it in that window, the real bind fails
27//! with its own "address in use", which is the same error the caller would have had anyway.
28//!
29//! [`ProtocolMetadataV2::well_known_port`]: crate::protocol::metadata::ProtocolMetadataV2::well_known_port
30
31use crate::llm::actions::Server;
32use crate::privilege::SystemCapabilities;
33use crate::protocol::metadata::PortTransport;
34
35/// The host a socket server binds when the caller and the protocol name none.
36///
37/// The same loopback default `server_startup.rs` falls back to, so the in-use probe asks about
38/// the address the server will actually take.
39pub const DEFAULT_HOST: &str = "127.0.0.1";
40
41/// Why a server is not starting on its well-known port.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum PortFallback {
44    /// The port is below 1024 and this process cannot bind privileged ports.
45    NeedsPrivilege,
46    /// Something already holds the port on the address the server would bind.
47    InUse,
48}
49
50/// The resolved default for one server start.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct DefaultPort {
53    /// The port to bind. `0` asks the OS for a free one.
54    pub port: u16,
55    /// The protocol's declared well-known port, if it has one.
56    pub well_known: Option<u16>,
57    /// The transport that port is registered for.
58    pub transport: PortTransport,
59    /// Set when the well-known port exists but is not being used, and why.
60    pub fallback: Option<PortFallback>,
61}
62
63impl DefaultPort {
64    /// One phrase for a picker line, a form's help text or a status line.
65    ///
66    /// Always names the well-known port when there is one, including when it is not being used,
67    /// so the reader learns both the number and why it was passed over.
68    pub fn describe(&self) -> String {
69        match (self.well_known, self.fallback) {
70            (Some(p), None) => format!("well-known port {p}"),
71            (Some(p), Some(PortFallback::NeedsPrivilege)) => {
72                format!("well-known port {p} needs root; starting on an OS-assigned port")
73            }
74            (Some(p), Some(PortFallback::InUse)) => {
75                format!("well-known port {p} is in use; starting on an OS-assigned port")
76            }
77            (None, _) if self.port == 0 => {
78                "no well-known port; starting on an OS-assigned port".to_string()
79            }
80            (None, _) => format!("port {}", self.port),
81        }
82    }
83}
84
85/// Resolve the default port from a declared well-known port.
86///
87/// Pure except for the in-use probe; see the module documentation for the rule.
88pub fn resolve_default_port(
89    well_known: Option<u16>,
90    transport: PortTransport,
91    host: &str,
92    caps: &SystemCapabilities,
93) -> DefaultPort {
94    let Some(port) = well_known.filter(|p| *p != 0) else {
95        return DefaultPort {
96            port: 0,
97            well_known: None,
98            transport,
99            fallback: None,
100        };
101    };
102    let fallback = if port < 1024 && !caps.can_bind_privileged_ports {
103        Some(PortFallback::NeedsPrivilege)
104    } else if port_is_in_use(host, port, transport) {
105        Some(PortFallback::InUse)
106    } else {
107        None
108    };
109    DefaultPort {
110        port: if fallback.is_some() { 0 } else { port },
111        well_known: Some(port),
112        transport,
113        fallback,
114    }
115}
116
117/// Whether `host:port` is already held by someone else, for the given transport.
118///
119/// Only an `AddrInUse` answer counts. Every other failure — permission, an address that is not
120/// ours, a platform without sockets (the wasm build's virtual network) — says nothing about
121/// whether the port is taken, and the real bind will report it precisely.
122pub fn port_is_in_use(host: &str, port: u16, transport: PortTransport) -> bool {
123    let result = match transport {
124        PortTransport::Tcp => std::net::TcpListener::bind((host, port)).map(drop),
125        PortTransport::Udp => std::net::UdpSocket::bind((host, port)).map(drop),
126        // The standard library has no SCTP socket; the real bind reports a taken port.
127        PortTransport::Sctp => return false,
128    };
129    matches!(result, Err(e) if e.kind() == std::io::ErrorKind::AddrInUse)
130}
131
132/// The default port for a server protocol when the caller names none, or `None` when a port
133/// means nothing to it (an interface, a pipe, a pty).
134///
135/// - A declared well-known port goes through [`resolve_default_port`].
136/// - Otherwise the protocol's own `default_binding()` port, when it declares a binding: `None`
137///   there is the protocol saying it binds no port at all.
138/// - Otherwise `0`. A protocol with neither is a socket server with no port of its own, and an
139///   OS-assigned port is the only default that cannot collide.
140///
141/// `host` is the address the caller asked for; `None` means the protocol's own default host,
142/// then [`DEFAULT_HOST`].
143pub fn default_port_for_server(
144    protocol: &dyn Server,
145    host: Option<&str>,
146    caps: &SystemCapabilities,
147) -> Option<DefaultPort> {
148    let metadata = protocol.metadata();
149    let binding = protocol.default_binding();
150    if let Some(well_known) = metadata.well_known_port {
151        let binding_host = binding.as_ref().and_then(|b| b.host.clone());
152        let host = host
153            .map(str::to_string)
154            .or(binding_host)
155            .unwrap_or_else(|| DEFAULT_HOST.to_string());
156        return Some(resolve_default_port(
157            Some(well_known),
158            metadata.well_known_transport,
159            &host,
160            caps,
161        ));
162    }
163    let port = match binding {
164        Some(b) => b.port?,
165        None => 0,
166    };
167    Some(DefaultPort {
168        port,
169        well_known: None,
170        transport: metadata.well_known_transport,
171        fallback: None,
172    })
173}