LAN opt-in, mDNS, security hardening, path routing #12

Merged
razvandimescu merged 13 commits from feat/community-feedback-improvements into main 2026-03-23 19:55:20 +08:00
razvandimescu commented 2026-03-23 12:58:21 +08:00 (Migrated from github.com)

Summary

  • LAN discovery opt-in — disabled by default, no unsolicited multicast traffic
  • mDNS migration — replaced custom JSON multicast (239.255.70.78:5390) with standard mDNS (_numa._tcp.local on 224.0.0.251:5353), reusing existing DNS parser. Instance ID in TXT for multi-instance self-filtering
  • Security hardening — API and proxy bind 127.0.0.1 by default; auto 0.0.0.0 when LAN is enabled. Configurable via api_bind_addr and proxy bind_addr
  • Path-based routing — longest prefix match with optional prefix stripping (strip = true). Segment-boundary matching prevents /api from matching /apiary. REST API: GET/POST/DELETE /services/{name}/routes
  • Dashboard — shows route lines per service when configured
  • Unit tests — 22 tests covering route matching (segment boundary, longest prefix, strip), config defaults (LAN disabled, localhost binds), and service store CRUD

Closes #11

Test plan

  • sudo numa starts with LAN disabled (no multicast traffic)
  • Enable [lan] enabled = true → mDNS announcements visible via dns-sd -B _numa._tcp
  • Two Numa instances on same host filter each other correctly via instance ID
  • API binds 127.0.0.1 by default; LAN hosts cannot reach dashboard
  • Set api_bind_addr = "0.0.0.0" → dashboard accessible from LAN
  • curl -X POST localhost:5380/services/app/routes -d '{"path":"/api","port":5001}' → route created (covered by unit tests)
  • app.numa/api/users → proxied to :5001/api/users (covered by unit tests — resolve_route)
  • app.numa/apiary → NOT matched by /api route (segment boundary) (covered by unit tests)
  • Route with strip: true strips prefix correctly (covered by unit tests)
  • Dashboard shows route lines under services with routes configured
  • make all passes (fmt, clippy, audit, build) (CI green)

🤖 Generated with Claude Code

## Summary - **LAN discovery opt-in** — disabled by default, no unsolicited multicast traffic - **mDNS migration** — replaced custom JSON multicast (239.255.70.78:5390) with standard mDNS (`_numa._tcp.local` on 224.0.0.251:5353), reusing existing DNS parser. Instance ID in TXT for multi-instance self-filtering - **Security hardening** — API and proxy bind `127.0.0.1` by default; auto `0.0.0.0` when LAN is enabled. Configurable via `api_bind_addr` and proxy `bind_addr` - **Path-based routing** — longest prefix match with optional prefix stripping (`strip = true`). Segment-boundary matching prevents `/api` from matching `/apiary`. REST API: `GET/POST/DELETE /services/{name}/routes` - **Dashboard** — shows route lines per service when configured - **Unit tests** — 22 tests covering route matching (segment boundary, longest prefix, strip), config defaults (LAN disabled, localhost binds), and service store CRUD Closes #11 ## Test plan - [x] `sudo numa` starts with LAN disabled (no multicast traffic) - [x] Enable `[lan] enabled = true` → mDNS announcements visible via `dns-sd -B _numa._tcp` - [x] Two Numa instances on same host filter each other correctly via instance ID - [x] API binds `127.0.0.1` by default; LAN hosts cannot reach dashboard - [x] Set `api_bind_addr = "0.0.0.0"` → dashboard accessible from LAN - [x] `curl -X POST localhost:5380/services/app/routes -d '{"path":"/api","port":5001}'` → route created *(covered by unit tests)* - [x] `app.numa/api/users` → proxied to `:5001/api/users` *(covered by unit tests — resolve_route)* - [x] `app.numa/apiary` → NOT matched by `/api` route (segment boundary) *(covered by unit tests)* - [x] Route with `strip: true` strips prefix correctly *(covered by unit tests)* - [x] Dashboard shows route lines under services with routes configured - [x] `make all` passes (fmt, clippy, audit, build) *(CI green)* 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.