Skip to content

Commit 4b26f60

Browse files
committed
fix(daemon): port probe + bind on both IPv4 and IPv6 loopback (v0.2.9)
v0.2.8 probe only checked 127.0.0.1:PORT. On macOS with AirPlay Receiver running, the daemon cleanly bound 127.0.0.1:5000 while AirTunes held ::1:5000 (both listen on *:5000 wildcard with SO_REUSEADDR, so the probe succeeded). Electron's frontend hits `http://localhost:5000/` which on macOS resolves `::1` first — and got 403 Forbidden from AirTunes instead of the daemon's 401/200. UI stuck on "waiting for backend" forever; daemon itself was fine if you curl'd 127.0.0.1 directly. Fix: probe both loopback families per port, pick only one where BOTH are free; then bind Kestrel to `http://127.0.0.1:PORT` AND `http://[::1]:PORT` so either resolution hits the daemon. Verified locally on a machine running AirTunes: lsof shows daemon on 127.0.0.1:5000 + [::1]:5000, ControlCenter on *:5000 (both families), `curl http://localhost:5000/healthz` → Kestrel (was AirTunes 403).
1 parent 6d97be9 commit 4b26f60

4 files changed

Lines changed: 28 additions & 14 deletions

File tree

src/daemon/NKS.WebDevConsole.Daemon/NKS.WebDevConsole.Daemon.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@
3333
<!-- F92: single source of truth for daemon version. Keep in sync with
3434
src/frontend/package.json on every bump so /api/status and
3535
/api/system return the real shipped version instead of hardcoded "0.1.0". -->
36-
<Version>0.2.8</Version>
37-
<AssemblyVersion>0.2.8.0</AssemblyVersion>
38-
<FileVersion>0.2.8.0</FileVersion>
39-
<InformationalVersion>0.2.8</InformationalVersion>
36+
<Version>0.2.9</Version>
37+
<AssemblyVersion>0.2.9.0</AssemblyVersion>
38+
<FileVersion>0.2.9.0</FileVersion>
39+
<InformationalVersion>0.2.9</InformationalVersion>
4040
<!-- Default release manifest: requireAdministrator (one UAC at
4141
launch, every child op inherits elevation). CI smoke test
4242
jobs pass -p:CiBuild=true to swap in app.ci.manifest which

src/daemon/NKS.WebDevConsole.Daemon/Program.cs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,22 +212,36 @@
212212

213213
// Port probing: 5000 is ASP.NET's default but macOS Sequoia's AirPlay
214214
// Receiver (ControlCenter) squats on it with SO_REUSEADDR so the daemon
215-
// can't bind. Try 5000–5019 and use whichever is free. Electron reads
216-
// the actual port from the tmp port file so it reconnects correctly.
215+
// can't bind cleanly. We need BOTH loopback families free — Electron's
216+
// frontend connects to `localhost:PORT` which resolves to `::1` first on
217+
// macOS, and if AirTunes owns `::1:5000` the client gets 403 Forbidden
218+
// from AirTunes while our Kestrel listens happily on 127.0.0.1:5000.
219+
// Probe IPv4 + IPv6 and only pick a port where both are free.
217220
int chosenPort = 5000;
218221
for (int p = 5000; p < 5020; p++)
222+
{
223+
if (!IsLoopbackPortFree(System.Net.IPAddress.Loopback, p)) continue;
224+
if (!IsLoopbackPortFree(System.Net.IPAddress.IPv6Loopback, p)) continue;
225+
chosenPort = p;
226+
break;
227+
}
228+
static bool IsLoopbackPortFree(System.Net.IPAddress addr, int port)
219229
{
220230
try
221231
{
222-
using var probe = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Loopback, p);
232+
using var probe = new System.Net.Sockets.TcpListener(addr, port);
223233
probe.Start();
224234
probe.Stop();
225-
chosenPort = p;
226-
break;
235+
return true;
236+
}
237+
catch (System.Net.Sockets.SocketException)
238+
{
239+
return false;
227240
}
228-
catch (System.Net.Sockets.SocketException) { /* try next */ }
229241
}
230-
builder.WebHost.UseUrls($"http://127.0.0.1:{chosenPort}");
242+
// Bind to BOTH families so `localhost` (→ ::1) and `127.0.0.1` both hit
243+
// the daemon. Kestrel accepts multiple --urls values.
244+
builder.WebHost.UseUrls($"http://127.0.0.1:{chosenPort}", $"http://[::1]:{chosenPort}");
231245

232246
var app = builder.Build();
233247
app.UseWebSockets();

src/frontend/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nks-hub/webdev-console",
3-
"version": "0.2.8",
3+
"version": "0.2.9",
44
"description": "Unified local development environment for Windows, macOS, and Linux \u2014 manage Apache/Nginx, PHP versions, MySQL/MariaDB, and SSL from one interface.",
55
"author": {
66
"name": "NKS Hub",

0 commit comments

Comments
 (0)