diff --git a/app/services/cwmp/state_machine_manager.ts b/app/services/cwmp/state_machine_manager.ts index 6976622..6c21492 100644 --- a/app/services/cwmp/state_machine_manager.ts +++ b/app/services/cwmp/state_machine_manager.ts @@ -322,11 +322,46 @@ export class StateMachineManager extends EventEmitter { } /** - * Ensure session is bound when TCP connection changes + * Ensure session is bound when TCP connection changes. + * + * First tries an exact remoteKey match. If that fails (CPE opened a new TCP + * port for the empty POST — standard TR-069 behaviour), falls back to an + * IP-only match among RUNNING machines (single unambiguous candidate only). + * In both cases the new sessionId is registered in sessionMachines so that + * hasSession() returns true and continue() can run. */ ensureSessionForRemote(remoteKey: string, newSession: string): boolean { - const deviceKey = this.remoteToDevice.get(remoteKey) - if (!deviceKey) return false + // --- Exact match path --- + let deviceKey = this.remoteToDevice.get(remoteKey) + + // --- IP-only fallback (CPE reconnected on a new TCP port) --- + if (!deviceKey) { + const lastColon = remoteKey.lastIndexOf(':') + const remoteIp = lastColon > 0 ? remoteKey.slice(0, lastColon).replace(/^\[|\]$/g, '') : remoteKey + + const candidates: string[] = [] + for (const [key, dkJson] of this.remoteToDevice.entries()) { + const kLastColon = key.lastIndexOf(':') + const keyIp = (kLastColon > 0 ? key.slice(0, kLastColon) : key).replace(/^\[|\]$/g, '') + if (keyIp !== remoteIp) continue + + const existSession = this.deviceSessions.get(dkJson) + if (!existSession) continue + const mid = this.sessionMachines.get(existSession) + if (!mid) continue + const m = this.machines.get(mid) + if (!m || m.status !== MachineStatus.RUNNING) continue + + if (!candidates.includes(dkJson)) candidates.push(dkJson) + } + + // Refuse to guess if ambiguous (NAT/CGNAT with multiple active devices) + if (candidates.length !== 1) return false + deviceKey = candidates[0] + + // Register the new remoteKey so future lookups (exact or fallback) hit immediately + this.bindRemoteToDevice(remoteKey, JSON.parse(deviceKey)) + } const existingSession = this.deviceSessions.get(deviceKey) if (!existingSession) return false @@ -338,15 +373,17 @@ export class StateMachineManager extends EventEmitter { if (!machine) return false // Don't rebind to a completed/failed machine - if (machine.status !== MachineStatus.RUNNING) { - return false - } + if (machine.status !== MachineStatus.RUNNING) return false if (existingSession !== newSession) { this.sessionMachines.delete(existingSession) this.sessionMachines.set(newSession, machineId) this.deviceSessions.set(deviceKey, newSession) machine.session = newSession + logger.debug( + { oldSession: existingSession, newSession, deviceKey }, + '[StateMachineManager] Session rebound via IP fallback (CPE new TCP port)' + ) } return true