fix: rebind CWMP session when CPE sends empty POST on new TCP port
TR-069 CPEs routinely open a new TCP connection (new port) for the empty POST that follows InformResponse. ensureSessionForRemote() only did an exact remoteKey match, so hasSession() returned false and the state machine was never continued — GPN discovery was never triggered. Add IP-only fallback in ensureSessionForRemote(): when the exact remoteKey is not found, scan RUNNING machines for a unique IP match, rebind the new remoteKey, and remap sessionMachines to the new sessionId. Ambiguous cases (NAT/CGNAT with multiple active devices on same IP) return false safely.
This commit is contained in:
1 parent
7835f99657
commit
61bfb4e762
1 file changed
+43
-6
@@ -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
|
||||
|
||||
Reference in new issue
Block a user