diff --git a/core/src/net/gateway.rs b/core/src/net/gateway.rs index e9f58881e..49bf35a23 100644 --- a/core/src/net/gateway.rs +++ b/core/src/net/gateway.rs @@ -185,6 +185,16 @@ struct CheckPortParams { #[serde(rename_all = "camelCase")] #[ts(export)] pub struct CheckPortRes { + pub ip: Ipv4Addr, + pub port: u16, + pub open_externally: bool, + pub open_internally: bool, + pub hairpinning: bool, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IfconfigPortRes { pub ip: Ipv4Addr, pub port: u16, pub reachable: bool, @@ -211,15 +221,33 @@ async fn check_port( ErrorKind::NotFound, ) })?; - let iface = &*ip_info.name; + + let internal_ips = ip_info + .subnets + .iter() + .map(|i| i.addr()) + .filter(|a| a.is_ipv4()) + .map(|a| SocketAddr::new(a, port)) + .collect::>(); + + let open_internally = tokio::time::timeout( + Duration::from_secs(5), + tokio::net::TcpStream::connect(&*internal_ips), + ) + .await + .map_or(false, |r| r.is_ok()); let client = reqwest::Client::builder(); #[cfg(target_os = "linux")] - let client = client.interface(iface); + let client = client.interface(gateway.as_str()); let url = base_url .join(&format!("/port/{port}")) .with_kind(ErrorKind::ParseUrl)?; - let res: CheckPortRes = client + let IfconfigPortRes { + ip, + port, + reachable: open_externally, + } = client .build()? .get(url) .timeout(Duration::from_secs(10)) @@ -228,7 +256,21 @@ async fn check_port( .error_for_status()? .json() .await?; - Ok(res) + + let hairpinning = tokio::time::timeout( + Duration::from_secs(5), + tokio::net::TcpStream::connect(SocketAddr::new(ip.into(), port)), + ) + .await + .map_or(false, |r| r.is_ok()); + + Ok(CheckPortRes { + ip, + port, + open_externally, + open_internally, + hairpinning, + }) } #[derive(Debug, Clone, Deserialize, Serialize, Parser, TS)] diff --git a/docs/TODO.md b/docs/TODO.md index 62d80fdfe..71465955f 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -23,15 +23,6 @@ Pending tasks for AI agents. Remove items when completed. other crate types. Extracting them requires either moving the type definitions into the sub-crate (and importing them back into `start-os`) or restructuring to share a common types crate. -- [ ] Make `SetupExecuteParams.password` optional in the backend - @dr-bonez - - **Problem**: In `core/src/setup.rs`, `SetupExecuteParams` has `password: EncryptedWire` (non-nullable), - but the frontend needs to send `null` for restore/transfer flows where the user keeps their existing - password. The `AttachParams` type correctly uses `Option` for this purpose. - - **Fix**: Change `password: EncryptedWire` to `password: Option` in `SetupExecuteParams` - and handle the `None` case in the `execute` handler (similar to how `attach` handles it). - - [ ] Auto-configure port forwards via UPnP/NAT-PMP/PCP - @dr-bonez **Goal**: When a binding is marked public, automatically configure port forwards on the user's router @@ -39,10 +30,11 @@ Pending tasks for AI agents. Remove items when completed. displaying manual instructions (the port forward mapping from patch-db) when auto-configuration is unavailable or fails. -- [ ] Decouple createTask from service running state - @dr-bonez +- [ ] Use TLS-ALPN challenges for check-port when addSsl - @dr-bonez - **Problem**: `createTask` currently depends on the service being in a running state. + **Problem**: The `check_port` RPC in `core/src/net/gateway.rs` currently uses an external HTTP + service (`ifconfig_url`) to verify port reachability. This doesn't check whether the port is forwarded to the right place, just that it's open. there's nothing we can do about this if it's a raw forward, but if it goes through the ssl proxy we can do a better verification. - **Goal**: The `input-not-matches` handler in StartOS should queue the task, check it once the - service is ready, then clear it if it matches. This allows tasks to be created regardless of - whether the service is currently running. + **Goal**: When a binding has `addSsl` enabled, use TLS-ALPN-01 challenges to verify port + reachability instead of (or in addition to) the plain TCP check. This more accurately validates + that the SSL port is properly configured and reachable. diff --git a/sdk/base/lib/osBindings/CheckPortRes.ts b/sdk/base/lib/osBindings/CheckPortRes.ts index 21bf8ce66..d50296885 100644 --- a/sdk/base/lib/osBindings/CheckPortRes.ts +++ b/sdk/base/lib/osBindings/CheckPortRes.ts @@ -1,3 +1,9 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type CheckPortRes = { ip: string; port: number; reachable: boolean } +export type CheckPortRes = { + ip: string + port: number + openExternally: boolean + openInternally: boolean + hairpinning: boolean +}