From d44d89655656d72ce19b24dde0396a2e32b00a79 Mon Sep 17 00:00:00 2001 From: Kevin L'Hereec Date: Fri, 17 Jul 2026 14:24:44 +0200 Subject: [PATCH] fix: persist empty POST in TR-069 trace when CPE uses a new TCP port When a CPE sends an empty POST (polling) on a new TCP connection, the remoteKey (IP:port) differs from the one registered during Inform. Add StateMachineManager.getHashIdForRemoteIp() to find the device by IP alone as a fallback, so cwmpDeviceId is resolved and the exchange is properly stored in the trace (cwmpMethod: Empty, dashed arrow). --- app/controllers/cwmp_controller.ts | 3 +++ app/services/cwmp/state_machine_manager.ts | 25 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/app/controllers/cwmp_controller.ts b/app/controllers/cwmp_controller.ts index 1589977..1926c69 100644 --- a/app/controllers/cwmp_controller.ts +++ b/app/controllers/cwmp_controller.ts @@ -40,7 +40,10 @@ export default class CwmpController { try { if (isEmpty) { const rebound = manager.ensureSessionForRemote(remoteKey, sessionId) + // Try exact remoteKey first (same TCP connection), then fall back to IP-only + // (CPE may open a new TCP port for the empty POST) const hashId = manager.getHashIdForRemote(remoteKey) + ?? manager.getHashIdForRemoteIp(request.ip()) if (hashId) ctx.cwmpDeviceId = hashId logger.debug( diff --git a/app/services/cwmp/state_machine_manager.ts b/app/services/cwmp/state_machine_manager.ts index 4e38052..d1650c7 100644 --- a/app/services/cwmp/state_machine_manager.ts +++ b/app/services/cwmp/state_machine_manager.ts @@ -248,6 +248,31 @@ export class StateMachineManager extends EventEmitter { } } + /** + * Fallback lookup: returns the hash identifier for any active device whose + * last known remote IP matches the given IP (ignoring port). + * Used when a CPE opens a new TCP connection for an empty POST and the + * exact remoteKey (IP:port) is not in the map. + */ + getHashIdForRemoteIp(remoteIp: string): string | undefined { + for (const [key, deviceKeyJson] of this.remoteToDevice.entries()) { + const keyIp = key.split(':').slice(0, -1).join(':') // strip port (handles IPv6 too) + if (keyIp !== remoteIp) continue + try { + const deviceId = JSON.parse(deviceKeyJson) + return getDeviceHashIdentifier({ + manufacturer: deviceId.manufacturer ?? '', + oui: deviceId.oui, + productClass: deviceId.productClass, + serialNumber: deviceId.serialNumber, + }) + } catch { + return undefined + } + } + return undefined + } + /** * Ensure session is bound when TCP connection changes */