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 */