diff --git a/clients/cascades-tucson/docs/network/network-optimization-master-plan.md b/clients/cascades-tucson/docs/network/network-optimization-master-plan.md index b1c18e42..8c911758 100644 --- a/clients/cascades-tucson/docs/network/network-optimization-master-plan.md +++ b/clients/cascades-tucson/docs/network/network-optimization-master-plan.md @@ -85,12 +85,20 @@ expect retry back down from ~23% and satisfaction recovering. for z in "Floor 3" "Floor 1" "Floor 2" "Floor 4"; do \ apply-radio.sh cascades na width 40 --zone "$z" --apply; done # rollback: na width 80 ``` -**3b. Channel plan** (decision: INCLUDE clean-DFS for diversity vs non-DFS-only for resilience — DFS is -empirically clean here, 0 radar): +**3b. Channel plan — NON-DFS ONLY (decided 2026-06-18 after rigorous DFS verification).** +Use **UNII-1 (36–48) + UNII-3 (149–165) only**; do NOT use DFS channels (52–144) on this voice-critical +network. A precise radar-detection sweep (real `radar found`/`NOL` signatures, CAC/control housekeeping +excluded) found **ZERO genuine hits across all 53 DFS APs** — BUT the window was only ~21–23h (APs rebooted +~23h ago, the 6/17 outage). Near Davis-Monthan AFB + TUS (~10 mi), military radar is sporadic and a single +hit forces a 30-min channel vacate = **dropped calls** — unacceptable for voice. **Resilience > diversity.** +The lost 5 GHz channel count is covered by **6 GHz (Phase 2a) absorbing capacity** — this is WHY 6 GHz comes first. ``` SURVEY=.claude/tmp/cascades-survey.json; SURVEY_JSON=$SURVEY survey-collect.sh cascades -SURVEY_JSON=$SURVEY channel-plan.sh cascades na # dry-run; review; apply per zone w/ validation +SURVEY_JSON=$SURVEY channel-plan.sh cascades na # dry-run; CONSTRAIN to non-DFS (36-48,149-165); review; apply per zone ``` +**Periodic DFS monitoring:** the ~1-day window isn't conclusive, so add a recurring precise `dfs-check.sh` +(fold into the network-logging plan). Staying on non-DFS means a future hit can't affect us; the monitor just +confirms the choice stays right. **3c. Relieve AP 103 specifically** (it now carries Lauren + 11 others on a 75%-busy ch149): move it off 149 to a clean channel from the plan, 40 MHz. Verify Lauren `.202` retry drops after. **Gate:** 5 GHz retry down on the busy APs; AP 103 cu_total well under 50%; no client stranded. @@ -134,7 +142,7 @@ a clean channel from the plan, 40 MHz. Verify Lauren `.202` retry drops after. - The 6 straggler phones — Howard re-keying separately; they'll benefit from the RF work regardless. ## 7. Open decisions for Howard -1. **5 GHz channel plan:** include clean-DFS (more diversity, tiny resilience risk) vs non-DFS-only (max resilience, more co-channel)? Recommendation: include clean-DFS. +1. ~~**5 GHz channel plan:** clean-DFS vs non-DFS-only~~ — **RESOLVED 2026-06-18: NON-DFS ONLY** (UNII-1 36–48 + UNII-3 149–165). DFS sweep was clean but only a ~1-day window near Davis-Monthan/TUS; a radar vacate = dropped calls, so resilience wins. 6 GHz covers the capacity gap. (See Phase 3b.) 2. **QoS depth:** UniFi WMM + DSCP-honor only, or also a pfSense WAN priority queue/limiter for RTP? Recommendation: both (additive). 3. **802.11r** on CSCNet: enable for seamless voice roaming, or k/v only (safer for mixed phones)? Recommendation: k/v now, test r on one phone first. 4. Tonight's stopping point: Phases 1–2 alone are a legitimate, lower-risk night; 3–4 can be a second night. diff --git a/clients/cascades-tucson/docs/network/phase1-voice-qos-design.md b/clients/cascades-tucson/docs/network/phase1-voice-qos-design.md new file mode 100644 index 00000000..8f21f43f --- /dev/null +++ b/clients/cascades-tucson/docs/network/phase1-voice-qos-design.md @@ -0,0 +1,97 @@ +# Cascades — Phase 1: Voice QoS Design (VLAN 30) + +- **Created:** 2026-06-18 (Howard-Home / claude-main). Part of `network-optimization-master-plan.md` Phase 1. +- **Status:** DESIGN — for review, then build (Howard drives pfSense GUI). Nothing applied. +- **Risk:** LOW — additive, voice-only prioritization; rollback = disable the shaper. Main caution: size the + shaper bandwidth correctly (a wrong value can throttle throughput) → test before/after. + +## Objective +Guarantee voice quality under load by prioritizing VLAN 30 traffic end-to-end. **The phones register to a +CLOUD PBX (Vertical) over the internet**, so the bottleneck that breaks calls is **WAN upload saturation** +(someone uploading / cloud backup / OneDrive sync fills the uplink → voice RTP queues → jitter, dropped +audio). QoS keeps voice ahead of bulk data on the WAN. + +## The big advantage of the VLAN move +**All voice is now one subnet: `10.0.30.0/24`.** So QoS can match *all* voice by **source subnet** — no +need to guess SIP/RTP port ranges per PBX. This is the cleanest, most robust match criterion and it only +became possible because we isolated voice onto VLAN 30. + +## Current state (verified 2026-06-18) +- **No traffic shaper / limiter configured** on pfSense (clean build). +- **Dual-WAN:** WAN1 `igc0` (Cox Fiber, primary, 1G link), WAN2 `igc3` (Cox Coax, 2.5G link); `WAN_Group` + failover (`downlosslatency`). Shaping must be applied on **both** WAN interfaces. +- pfSense Plus 25.07 (ALTQ shaper + dummynet limiters available). + +## Three layers (in priority order) + +### Layer 1 — pfSense WAN shaper (PRIMARY — this is where calls break) +**Type: HFSC** (hierarchical, lets us guarantee voice a floor while letting it borrow idle bandwidth). +Per WAN interface, three queues: +| Queue | Role | HFSC settings (starting point) | +|---|---|---| +| `qVoice` | voice (VLAN 30 / DSCP EF) | **priority 7**, realtime ~30% of WAN-up, link-share 30%, NOT default | +| `qACK` | TCP ACKs (keeps downloads snappy) | priority 6, ~10% | +| `qDefault` | everything else | **default**, link-share ~60% | + +**Match rule (floating, WAN, direction out):** source `10.0.30.0/24` → `qVoice`. (Optionally also match +DSCP EF if phones mark it — see Layer 4.) One floating rule per WAN, or interface = WAN_Group. + +**Download side:** RTP from the PBX *to* the phones is shaped on the **LAN-side** queues. The wizard builds +both directions; if hand-building, mirror a `qVoice` on the internal interfaces too. Upload is the more +critical direction for cloud-PBX voice, but do both. + +**Build path (GUI — Howard drives):** +- Easiest: **Firewall → Traffic Shaper → Wizard → "Multiple Lan/Wan"** — set #WAN=2, #LAN as needed, + enter each WAN's bandwidth (below), on the VoIP page choose **"prioritize by address" = `10.0.30.0/24`** + with a guaranteed %; the wizard generates HFSC queues + the float rules. Then tune. +- Or manual: Firewall → Traffic Shaper → By Interface → add HFSC on WAN1 + WAN2, create the 3 queues, + then Firewall → Rules → Floating → match `10.0.30.0/24` out → Ackqueue/Queue = qACK/qVoice. + +> **INPUT NEEDED (the one missing number):** the **Cox plan UPLOAD speed** for WAN1 (fiber) and WAN2 (coax). +> Shape `qVoice`'s parent to ~**90–95% of actual upload** so the queue forms in pfSense (where we control +> priority), not at the ISP. The physical link is 1G/2.5G but the *plan* upload is what to shape to — get it +> from the Cox bill / a speedtest from a LAN host. Without it the shaper can't be sized correctly. + +### Layer 2 — UniFi WMM (the WiFi phones — Poly) +Over the air, **WMM** maps DSCP → WiFi access categories; voice (DSCP EF/46) → **WMM Voice AC** (gets TXOP +priority over data). WMM is ON by default on UniFi — **verify it's enabled on CSCNet** and that the U7 APs +honor DSCP→WMM. This is what protects the 22 Poly phones over the air during WiFi congestion. (Ties into the +RF work — a clean 5/6 GHz + WMM = good wireless voice.) + +### Layer 3 — UniFi switch QoS (the wired AudioCodes) +UniFi switches honor 802.1p/DSCP and queue tagged voice to a high-priority egress queue — mostly automatic +once the phones mark DSCP. LAN links are gig and rarely congested, so this is the least critical layer, but +confirm the USW isn't stripping DSCP and that voice VLAN 30 frames get the priority queue. + +### Layer 4 — DSCP marking (make the above reliable) +- **Verify the phones mark voice:** AudioCodes + Poly typically tag RTP **EF (46)** and signaling **CS3 (24)** + by default, often set via the PBX/provisioning. Confirm with Vertical (Richard) or capture a packet. +- **If they DON'T mark (or inconsistently):** add a pfSense floating rule that **SETS DSCP EF** on + `10.0.30.0/24` traffic (Advanced → "Match/Set DSCP"). Then Layer 1/2/3 can all match on EF too. +- **Match-by-subnet (Layer 1) works regardless of DSCP** — it's the safety net. DSCP makes WMM (Layer 2) + and switch QoS (Layer 3) automatic. + +## Implementation order +1. Get the Cox WAN upload numbers (blocker for Layer 1 sizing). +2. Confirm phones mark DSCP EF (Vertical) — decides whether we add the pfSense set-DSCP rule. +3. Build Layer 1 (pfSense HFSC + float rule) — dry-run mindset: set it, then validate. +4. Verify Layer 2 (WMM on CSCNet) + Layer 3 (switch honoring DSCP). +5. Validate (below). Tune `qVoice` % if needed. + +## Validation (prove it works) +- **Baseline:** from a LAN host, saturate the WAN upload (big upload / `iperf3 -u` / speedtest) WHILE on a + call from a voice phone — note the breakup *without* QoS. +- **After:** repeat the same saturation; call stays clean. Check Firewall → Traffic Shaper → Queues: `qVoice` + carrying voice with ~0 drops while `qDefault` absorbs the saturation + drops. +- Confirm both WANs (test on primary; fail to WAN2 and re-test). + +## Rollback +Firewall → Traffic Shaper → disable/remove the shaper; delete the floating rule. Zero residual effect +(QoS only orders packets under congestion; removing it reverts to FIFO). The set-DSCP rule (if added) can stay +or go independently. + +## Notes / interplay with the rest of the plan +- QoS is **independent of the RF work** — it helps wired + WiFi voice immediately and can be built tonight + regardless of the 2.4/5/6 GHz changes. +- It does NOT fix RF problems (a phone on a 50%-retry 2.4 GHz radio still suffers) — QoS handles *congestion/ + contention for bandwidth*, RF tuning handles *the air*. Both are needed; they're complementary. diff --git a/errorlog.md b/errorlog.md index 9727394b..907e6c00 100644 --- a/errorlog.md +++ b/errorlog.md @@ -17,6 +17,8 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure · +2026-06-19 | Howard-Home | git/sync-temp-files | [friction] RECURRED (3rd time): .fleet325.dev controller-scratch swept into commit by git add -A; my earlier fix only ignored specific names (.sta/.dev/.q*), missed .fleet*. Real fix: write API scratch OUTSIDE the repo (e.g. $TMPDIR), not CWD-relative dotfiles [ctx: ref=git/sync-temp-files prior entry; recurring] + 2026-06-18 | GURU-5070 | agy/search | gemini CLI threw ineligible/projectId setup error (throwIneligibleOrProjectIdError), empty response after 3 attempts [ctx: mode=search host=GURU-5070] 2026-06-18 | GURU-5070 | agy | gemini returned no response (empty after 3 attempts) [ctx: mode=search err= at process.processTicksAndRejections (node:internal/process/task_queues:104:]