Ignore session cookie on Inform

Some devices preserve cookies set in a previous session and pass them
on to the next session. The presence of a session cookie in the Inform
request causes an unnecessary DB query and, in some edge cases, an
Invalid Session error.
This commit is contained in:
Zaid Abdulla committed 2021-09-16 13:27:37 -07:00
1 parent bc2d258f9c
commit ca83704958
2 files changed
+111 -142

No files matched your search

+109 -139
View File
@@ -878,27 +878,6 @@ async function sendAcsRequest(
return writeResponse(sessionContext, res); return writeResponse(sessionContext, res);
} }
async function getSession(
connection: Socket,
sessionId: string
): Promise<SessionContext> {
let sessionContext = currentSessions.get(connection);
if (sessionContext) {
currentSessions.delete(connection);
return sessionContext;
}
if (!sessionId) return null;
await new Promise((resolve) => setTimeout(resolve, 100));
const sessionContextString = await cache.pop(`session_${sessionId}`);
if (!sessionContextString) return null;
sessionContext = await session.deserialize(sessionContextString);
connection.setTimeout(sessionContext.timeout * 1000);
return sessionContext;
}
// Only needed to prevent tree shaking from removing the remoteAddress // Only needed to prevent tree shaking from removing the remoteAddress
// workaround in onConnection function. // workaround in onConnection function.
const remoteAddressWorkaround = new WeakMap<Socket, string>(); const remoteAddressWorkaround = new WeakMap<Socket, string>();
@@ -1045,13 +1024,13 @@ async function reportBadState(sessionContext: SessionContext): Promise<void> {
sessionContext: sessionContext, sessionContext: sessionContext,
}); });
const httpResponse = sessionContext.httpResponse; const httpResponse = sessionContext.httpResponse;
currentSessions.delete(httpResponse.socket);
const body = "Bad session state"; const body = "Bad session state";
httpResponse.setHeader("Content-Length", Buffer.byteLength(body)); httpResponse.setHeader("Content-Length", Buffer.byteLength(body));
httpResponse.writeHead(400, { Connection: "close" }); httpResponse.writeHead(400, { Connection: "close" });
if (sessionContext.debug) if (sessionContext.debug)
debug.outgoingHttpResponse(httpResponse, sessionContext.deviceId, body); debug.outgoingHttpResponse(httpResponse, sessionContext.deviceId, body);
httpResponse.end(body); httpResponse.end(body);
return endSession(sessionContext);
} }
async function responseUnauthorized( async function responseUnauthorized(
@@ -1323,6 +1302,48 @@ export function listener(
}); });
} }
async function clientError(
httpRequest: IncomingMessage,
httpResponse: ServerResponse,
sessionContext: SessionContext,
body: string,
msg: string
): Promise<void> {
let debugEnabled = false;
let deviceId: string = null;
if (sessionContext) {
debugEnabled = sessionContext.debug;
deviceId = sessionContext.deviceId;
} else {
const cacheSnapshot = await localCache.getCurrentSnapshot();
debugEnabled = !!localCache.getConfig(
cacheSnapshot,
"cwmp.debug",
{
remoteAddress: getRequestOrigin(httpRequest).remoteAddress,
},
Date.now(),
(e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
return getRequestOrigin(httpRequest).remoteAddress;
return e;
}
);
}
httpResponse.setHeader("Content-Length", Buffer.byteLength(msg));
httpResponse.writeHead(400, { Connection: "close" });
if (debugEnabled) {
debug.incomingHttpRequest(httpRequest, deviceId, body);
debug.outgoingHttpResponse(httpResponse, deviceId, msg);
}
httpResponse.end(msg);
if (sessionContext) await endSession(sessionContext);
}
function decodeString(buffer: Buffer, charset: string): string { function decodeString(buffer: Buffer, charset: string): string {
try { try {
return buffer.toString(charset as BufferEncoding); return buffer.toString(charset as BufferEncoding);
@@ -1410,15 +1431,14 @@ async function listenerAsync(
// Request aborted // Request aborted
if (!body) return; if (!body) return;
const newConnection = !currentSessions.has(httpRequest.socket); let sessionContext = currentSessions.get(httpRequest.socket);
const sessionContext = await getSession(httpRequest.socket, sessionId);
if (sessionContext) { if (sessionContext) {
currentSessions.delete(httpRequest.socket);
sessionContext.httpRequest = httpRequest; sessionContext.httpRequest = httpRequest;
sessionContext.httpResponse = httpResponse; sessionContext.httpResponse = httpResponse;
if ( if (
(newConnection && sessionContext.sessionId !== sessionId) || sessionContext.sessionId !== sessionId ||
sessionContext.lastActivity + sessionContext.timeout * 1000 < Date.now() sessionContext.lastActivity + sessionContext.timeout * 1000 < Date.now()
) { ) {
logger.accessError({ logger.accessError({
@@ -1426,29 +1446,14 @@ async function listenerAsync(
sessionContext: sessionContext, sessionContext: sessionContext,
}); });
const _body = "Invalid session"; return clientError(
httpResponse.setHeader("Content-Length", Buffer.byteLength(_body)); httpRequest,
httpResponse.writeHead(400, { Connection: "close" });
if (sessionContext.debug) {
debug.outgoingHttpResponse(
httpResponse, httpResponse,
sessionContext.deviceId, sessionContext,
_body body.toString(),
"Invalid session"
); );
} }
httpResponse.end(_body);
await endSession(sessionContext);
return;
}
if (newConnection && sessionContext.authState !== 1)
sessionContext.authState = 0;
} else if (stats.concurrentRequests > MAX_CONCURRENT_REQUESTS) {
// Check again just in case device included old session ID
// from the previous session
httpResponse.writeHead(503, { "Retry-after": 60, Connection: "close" });
httpResponse.end("503 Service Unavailable");
stats.droppedRequests += 1;
return;
} }
let charset: string; let charset: string;
@@ -1468,6 +1473,16 @@ async function listenerAsync(
const bodyStr = decodeString(body, charset); const bodyStr = decodeString(body, charset);
if (bodyStr == null) { if (bodyStr == null) {
if (!sessionContext && sessionId) {
await new Promise((resolve) => setTimeout(resolve, 100));
const sessionContextString = await cache.pop(`session_${sessionId}`);
if (sessionContextString) {
sessionContext = await session.deserialize(sessionContextString);
sessionContext.httpRequest = httpRequest;
sessionContext.httpResponse = httpResponse;
}
}
const msg = `Unknown encoding '${charset}'`; const msg = `Unknown encoding '${charset}'`;
logger.accessError({ logger.accessError({
message: "XML parse error", message: "XML parse error",
@@ -1477,97 +1492,58 @@ async function listenerAsync(
httpResponse: httpResponse, httpResponse: httpResponse,
}, },
}); });
httpResponse.setHeader("Content-Length", Buffer.byteLength(msg)); return clientError(
httpResponse.writeHead(400, { Connection: "close" });
if (sessionContext) {
if (sessionContext.debug) {
debug.incomingHttpRequest(
httpRequest, httpRequest,
sessionContext.deviceId, httpResponse,
body.toString() sessionContext,
body.toString(),
msg
); );
debug.outgoingHttpResponse(httpResponse, sessionContext.deviceId, msg);
}
await endSession(sessionContext);
} else {
const cacheSnapshot = await localCache.getCurrentSnapshot();
const d = !!localCache.getConfig(
cacheSnapshot,
"cwmp.debug",
{
remoteAddress: getRequestOrigin(httpRequest).remoteAddress,
},
Date.now(),
(e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
return getRequestOrigin(httpRequest).remoteAddress;
return e;
}
);
if (d) {
debug.incomingHttpRequest(httpRequest, null, body.toString());
debug.outgoingHttpResponse(httpResponse, null, msg);
}
}
httpResponse.end(msg);
return;
} }
const parseWarnings = []; const parseWarnings = [];
let rpc: SoapMessage; let rpc: SoapMessage;
try { try {
rpc = soap.request( rpc = soap.request(bodyStr, parseWarnings);
bodyStr, } catch (err) {
sessionContext ? sessionContext.cwmpVersion : null, if (!sessionContext && sessionId) {
parseWarnings await new Promise((resolve) => setTimeout(resolve, 100));
); const sessionContextString = await cache.pop(`session_${sessionId}`);
} catch (error) { if (sessionContextString) {
sessionContext = await session.deserialize(sessionContextString);
sessionContext.httpRequest = httpRequest;
sessionContext.httpResponse = httpResponse;
}
}
logger.accessError({ logger.accessError({
message: "XML parse error", message: "XML parse error",
parseError: error.message.trim(), parseError: err.message,
sessionContext: sessionContext || { sessionContext: sessionContext || {
httpRequest: httpRequest, httpRequest: httpRequest,
httpResponse: httpResponse, httpResponse: httpResponse,
}, },
}); });
httpResponse.setHeader("Content-Length", Buffer.byteLength(error.message));
httpResponse.writeHead(400, { Connection: "close" }); return clientError(
if (sessionContext) {
if (sessionContext.debug) {
debug.incomingHttpRequest(
httpRequest, httpRequest,
sessionContext.deviceId,
bodyStr
);
debug.outgoingHttpResponse(
httpResponse, httpResponse,
sessionContext.deviceId, sessionContext,
error.message bodyStr,
err.message
); );
} }
await endSession(sessionContext);
} else { if (!sessionContext && sessionId && rpc.cpeRequest?.name !== "Inform") {
const cacheSnapshot = await localCache.getCurrentSnapshot(); await new Promise((resolve) => setTimeout(resolve, 100));
const d = !!localCache.getConfig( const sessionContextString = await cache.pop(`session_${sessionId}`);
cacheSnapshot, if (sessionContextString) {
"cwmp.debug", sessionContext = await session.deserialize(sessionContextString);
{ sessionContext.httpRequest = httpRequest;
remoteAddress: getRequestOrigin(httpRequest).remoteAddress, sessionContext.httpResponse = httpResponse;
}, httpRequest.socket.setTimeout(sessionContext.timeout * 1000);
Date.now(), if (sessionContext.authState !== 1) sessionContext.authState = 0;
(e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
return getRequestOrigin(httpRequest).remoteAddress;
return e;
} }
);
if (d) {
debug.incomingHttpRequest(httpRequest, null, bodyStr);
debug.outgoingHttpResponse(httpResponse, null, error.message);
}
}
httpResponse.end(error.message);
return;
} }
if (sessionContext) if (sessionContext)
@@ -1576,33 +1552,27 @@ async function listenerAsync(
if (!(rpc.cpeRequest && rpc.cpeRequest.name === "Inform")) { if (!(rpc.cpeRequest && rpc.cpeRequest.name === "Inform")) {
logger.accessError({ logger.accessError({
message: "Invalid session", message: "Invalid session",
sessionContext: sessionContext || { sessionContext: {
httpRequest: httpRequest, httpRequest: httpRequest,
httpResponse: httpResponse, httpResponse: httpResponse,
}, },
}); });
const _body = "Invalid session";
httpResponse.setHeader("Content-Length", Buffer.byteLength(_body)); return clientError(
httpResponse.writeHead(400, { Connection: "close" }); httpRequest,
const cacheSnapshot = await localCache.getCurrentSnapshot(); httpResponse,
const d = !!localCache.getConfig( null,
cacheSnapshot, bodyStr,
"cwmp.debug", "Invalid session"
{
remoteAddress: getRequestOrigin(httpRequest).remoteAddress,
},
Date.now(),
(e) => {
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
return getRequestOrigin(httpRequest).remoteAddress;
return e;
}
); );
if (d) {
debug.incomingHttpRequest(httpRequest, null, bodyStr);
debug.outgoingHttpResponse(httpResponse, null, _body);
} }
httpResponse.end(_body);
if (stats.concurrentRequests > MAX_CONCURRENT_REQUESTS) {
// Check again just in case device included old session ID
// from the previous session
httpResponse.writeHead(503, { "Retry-after": 60, Connection: "close" });
httpResponse.end("503 Service Unavailable");
stats.droppedRequests += 1;
return; return;
} }
+2 -3
View File
@@ -827,14 +827,13 @@ function fault(xml: Element): CpeFault {
export function request( export function request(
body: string, body: string,
cwmpVersion: string,
warn: Record<string, unknown>[] warn: Record<string, unknown>[]
): SoapMessage { ): SoapMessage {
warnings = warn; warnings = warn;
const rpc = { const rpc = {
id: null, id: null,
cwmpVersion: cwmpVersion, cwmpVersion: null,
sessionTimeout: null, sessionTimeout: null,
cpeRequest: null, cpeRequest: null,
cpeFault: null, cpeFault: null,
@@ -877,7 +876,7 @@ export function request(
const methodElement = bodyElement.children[0]; const methodElement = bodyElement.children[0];
if (!rpc.cwmpVersion && methodElement.localName !== "Fault") { if (methodElement.localName === "Inform") {
let namespace, namespaceHref; let namespace, namespaceHref;
for (const e of [methodElement, bodyElement, envelope]) { for (const e of [methodElement, bodyElement, envelope]) {
namespace = namespace || e.namespace; namespace = namespace || e.namespace;