Deubg log for HTTP and HTTPS client errors
This commit is contained in:
1 parent
fa95d7a310
commit
c9a4be0475
6 files changed
+99
-17
No files matched your search
@@ -83,6 +83,7 @@ if (!cluster.worker) {
|
|||||||
host: SERVICE_ADDRESS,
|
host: SERVICE_ADDRESS,
|
||||||
ssl: key && cert ? { key, cert } : null,
|
ssl: key && cert ? { key, cert } : null,
|
||||||
onConnection: cwmp.onConnection,
|
onConnection: cwmp.onConnection,
|
||||||
|
onClientError: cwmp.onClientError,
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
keepAliveTimeout: 0,
|
keepAliveTimeout: 0,
|
||||||
};
|
};
|
||||||
|
|||||||
+27
-8
@@ -54,6 +54,7 @@ import { decode, encodingExists } from "iconv-lite";
|
|||||||
import { parseXmlDeclaration } from "./xml-parser";
|
import { parseXmlDeclaration } from "./xml-parser";
|
||||||
import * as debug from "./debug";
|
import * as debug from "./debug";
|
||||||
import { getRequestOrigin } from "./forwarded";
|
import { getRequestOrigin } from "./forwarded";
|
||||||
|
import { getSocketEndpoints } from "./server";
|
||||||
|
|
||||||
const gzipPromisified = promisify(zlib.gzip);
|
const gzipPromisified = promisify(zlib.gzip);
|
||||||
const deflatePromisified = promisify(zlib.deflate);
|
const deflatePromisified = promisify(zlib.deflate);
|
||||||
@@ -883,16 +884,8 @@ async function sendAcsRequest(
|
|||||||
return writeResponse(sessionContext, res);
|
return writeResponse(sessionContext, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only needed to prevent tree shaking from removing the remoteAddress
|
|
||||||
// workaround in onConnection function.
|
|
||||||
const remoteAddressWorkaround = new WeakMap<Socket, string>();
|
|
||||||
|
|
||||||
// When socket closes, store active sessions in cache
|
// When socket closes, store active sessions in cache
|
||||||
export function onConnection(socket: Socket): void {
|
export function onConnection(socket: Socket): void {
|
||||||
// The property remoteAddress may be undefined after the connection is
|
|
||||||
// closed, unless we read it at least once (caching?)
|
|
||||||
remoteAddressWorkaround.set(socket, socket.remoteAddress);
|
|
||||||
|
|
||||||
socket.on("close", async () => {
|
socket.on("close", async () => {
|
||||||
const sessionContext = currentSessions.get(socket);
|
const sessionContext = currentSessions.get(socket);
|
||||||
if (!sessionContext) return;
|
if (!sessionContext) return;
|
||||||
@@ -947,6 +940,32 @@ export function onConnection(socket: Socket): void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function onClientError(err: Error, socket: Socket): void {
|
||||||
|
const remoteAddress = getSocketEndpoints(socket).remoteAddress;
|
||||||
|
localCache
|
||||||
|
.getCurrentSnapshot()
|
||||||
|
.then((cacheSnapshot) => {
|
||||||
|
const debugEnabled = !!localCache.getConfig(
|
||||||
|
cacheSnapshot,
|
||||||
|
"cwmp.debug",
|
||||||
|
{
|
||||||
|
remoteAddress: remoteAddress,
|
||||||
|
},
|
||||||
|
Date.now(),
|
||||||
|
(e) => {
|
||||||
|
if (Array.isArray(e) && e[0] === "FUNC" && e[1] === "REMOTE_ADDRESS")
|
||||||
|
return remoteAddress;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (debugEnabled) debug.clientError(remoteAddress, err);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
if (stats.droppedRequests) {
|
if (stats.droppedRequests) {
|
||||||
logger.warn({
|
logger.warn({
|
||||||
|
|||||||
+23
-3
@@ -22,6 +22,7 @@ import { Socket } from "net";
|
|||||||
import { appendFileSync } from "fs";
|
import { appendFileSync } from "fs";
|
||||||
import { stringify } from "./common/yaml";
|
import { stringify } from "./common/yaml";
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
|
import { getSocketEndpoints } from "./server";
|
||||||
|
|
||||||
const DEBUG_FILE = "" + config.get("DEBUG_FILE");
|
const DEBUG_FILE = "" + config.get("DEBUG_FILE");
|
||||||
const DEBUG_FORMAT = "" + config.get("DEBUG_FORMAT");
|
const DEBUG_FORMAT = "" + config.get("DEBUG_FORMAT");
|
||||||
@@ -45,13 +46,14 @@ export function incomingHttpRequest(
|
|||||||
if (!DEBUG_FILE) return;
|
if (!DEBUG_FILE) return;
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const con = httpRequest.socket;
|
const con = httpRequest.socket;
|
||||||
|
const socketEndpoints = getSocketEndpoints(con);
|
||||||
const msg = {
|
const msg = {
|
||||||
event: "incoming HTTP request",
|
event: "incoming HTTP request",
|
||||||
timestamp: now,
|
timestamp: now,
|
||||||
remoteAddress: con.remoteAddress,
|
remoteAddress: socketEndpoints.remoteAddress,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
connection: getConnectionTimestamp(con),
|
connection: getConnectionTimestamp(con),
|
||||||
localPort: con.localPort,
|
localPort: socketEndpoints.localPort,
|
||||||
method: httpRequest.method,
|
method: httpRequest.method,
|
||||||
url: httpRequest.url,
|
url: httpRequest.url,
|
||||||
headers: httpRequest.headers,
|
headers: httpRequest.headers,
|
||||||
@@ -73,10 +75,11 @@ export function outgoingHttpResponse(
|
|||||||
if (!DEBUG_FILE) return;
|
if (!DEBUG_FILE) return;
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const con = httpResponse.socket;
|
const con = httpResponse.socket;
|
||||||
|
const socketEndpoints = getSocketEndpoints(con);
|
||||||
const msg = {
|
const msg = {
|
||||||
event: "outgoing HTTP response",
|
event: "outgoing HTTP response",
|
||||||
timestamp: now,
|
timestamp: now,
|
||||||
remoteAddress: con.remoteAddress,
|
remoteAddress: socketEndpoints.remoteAddress,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
connection: getConnectionTimestamp(con),
|
connection: getConnectionTimestamp(con),
|
||||||
statusCode: httpResponse.statusCode,
|
statusCode: httpResponse.statusCode,
|
||||||
@@ -199,3 +202,20 @@ export function outgoingUdpMessage(
|
|||||||
appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n");
|
appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n");
|
||||||
else throw new Error(`Unrecognized DEBUG_FORMAT option`);
|
else throw new Error(`Unrecognized DEBUG_FORMAT option`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function clientError(remoteAddress: string, err: Error): void {
|
||||||
|
if (!DEBUG_FILE) return;
|
||||||
|
const now = new Date();
|
||||||
|
const msg = {
|
||||||
|
event: "client error",
|
||||||
|
timestamp: now,
|
||||||
|
remoteAddress: remoteAddress,
|
||||||
|
error: err.message,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (DEBUG_FORMAT === "yaml")
|
||||||
|
appendFileSync(DEBUG_FILE, "---\n" + stringify(msg));
|
||||||
|
else if (DEBUG_FORMAT === "json")
|
||||||
|
appendFileSync(DEBUG_FILE, JSON.stringify(msg) + "\n");
|
||||||
|
else throw new Error(`Unrecognized DEBUG_FORMAT option`);
|
||||||
|
}
|
||||||
+7
-5
@@ -21,6 +21,7 @@ import { IncomingMessage } from "http";
|
|||||||
import { TLSSocket } from "tls";
|
import { TLSSocket } from "tls";
|
||||||
import { parseCIDR, parse, IPv6, IPv4 } from "ipaddr.js";
|
import { parseCIDR, parse, IPv6, IPv4 } from "ipaddr.js";
|
||||||
import * as config from "./config";
|
import * as config from "./config";
|
||||||
|
import { getSocketEndpoints } from "./server";
|
||||||
|
|
||||||
interface RequestOrigin {
|
interface RequestOrigin {
|
||||||
localAddress: string;
|
localAddress: string;
|
||||||
@@ -98,18 +99,19 @@ export function getRequestOrigin(request: IncomingMessage): RequestOrigin {
|
|||||||
let origin = cache.get(request);
|
let origin = cache.get(request);
|
||||||
if (!origin) {
|
if (!origin) {
|
||||||
const soc = request.socket;
|
const soc = request.socket;
|
||||||
|
const socketEndpoints = getSocketEndpoints(soc);
|
||||||
origin = {
|
origin = {
|
||||||
localAddress: soc.localAddress,
|
localAddress: socketEndpoints.localAddress,
|
||||||
localPort: soc.localPort,
|
localPort: socketEndpoints.localPort,
|
||||||
remoteAddress: soc.remoteAddress,
|
remoteAddress: socketEndpoints.remoteAddress,
|
||||||
remotePort: soc.remotePort,
|
remotePort: socketEndpoints.remotePort,
|
||||||
host: request.headers["host"],
|
host: request.headers["host"],
|
||||||
encrypted: !!(request.socket as TLSSocket).encrypted,
|
encrypted: !!(request.socket as TLSSocket).encrypted,
|
||||||
};
|
};
|
||||||
|
|
||||||
const header = request.headers["forwarded"];
|
const header = request.headers["forwarded"];
|
||||||
if (header) {
|
if (header) {
|
||||||
const ip = parse(soc.remoteAddress) as IPv4;
|
const ip = parse(socketEndpoints.remoteAddress) as IPv4;
|
||||||
if (cidrs.some((cidr) => ip.match(cidr as [IPv4, number]))) {
|
if (cidrs.some((cidr) => ip.match(cidr as [IPv4, number]))) {
|
||||||
const parsed = parseForwardedHeader(header);
|
const parsed = parseForwardedHeader(header);
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -30,6 +30,7 @@ import { version as VERSION } from "../package.json";
|
|||||||
import { ping } from "./ping";
|
import { ping } from "./ping";
|
||||||
import * as logger from "./logger";
|
import * as logger from "./logger";
|
||||||
import { flattenDevice } from "./mongodb-functions";
|
import { flattenDevice } from "./mongodb-functions";
|
||||||
|
import { getSocketEndpoints } from "./server";
|
||||||
|
|
||||||
const DEVICE_TASKS_REGEX = /^\/devices\/([a-zA-Z0-9\-_%]+)\/tasks\/?$/;
|
const DEVICE_TASKS_REGEX = /^\/devices\/([a-zA-Z0-9\-_%]+)\/tasks\/?$/;
|
||||||
const TASKS_REGEX = /^\/tasks\/([a-zA-Z0-9\-_%]+)(\/[a-zA-Z_]*)?$/;
|
const TASKS_REGEX = /^\/tasks\/([a-zA-Z0-9\-_%]+)(\/[a-zA-Z_]*)?$/;
|
||||||
@@ -101,10 +102,11 @@ export function listener(
|
|||||||
request.addListener("end", () => {
|
request.addListener("end", () => {
|
||||||
const body = getBody();
|
const body = getBody();
|
||||||
const urlParts = url.parse(request.url, true);
|
const urlParts = url.parse(request.url, true);
|
||||||
|
const socketEndpoints = getSocketEndpoints(request.socket);
|
||||||
|
|
||||||
logger.accessInfo(
|
logger.accessInfo(
|
||||||
Object.assign({}, urlParts.query, {
|
Object.assign({}, urlParts.query, {
|
||||||
remoteAddress: request.socket.remoteAddress,
|
remoteAddress: socketEndpoints.remoteAddress,
|
||||||
message: `${request.method} ${urlParts.pathname}`,
|
message: `${request.method} ${urlParts.pathname}`,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -59,8 +59,20 @@ interface ServerOptions {
|
|||||||
timeout?: number;
|
timeout?: number;
|
||||||
keepAliveTimeout?: number;
|
keepAliveTimeout?: number;
|
||||||
onConnection?: (socket: Socket) => void;
|
onConnection?: (socket: Socket) => void;
|
||||||
|
onClientError?: (err: Error, socket: Socket) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SocketEndpoint {
|
||||||
|
localAddress: string;
|
||||||
|
localPort: number;
|
||||||
|
remoteAddress: string;
|
||||||
|
remotePort: number;
|
||||||
|
remoteFamily: "IPv4" | "IPv6";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save this info as they're not accessible after a socket has been closed
|
||||||
|
const socketEndpoints: WeakMap<Socket, SocketEndpoint> = new WeakMap();
|
||||||
|
|
||||||
export function start(
|
export function start(
|
||||||
options: ServerOptions,
|
options: ServerOptions,
|
||||||
_listener: http.RequestListener
|
_listener: http.RequestListener
|
||||||
@@ -85,6 +97,27 @@ export function start(
|
|||||||
if (options.onConnection) server.on("connection", options.onConnection);
|
if (options.onConnection) server.on("connection", options.onConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server.on("connection", (socket: Socket) => {
|
||||||
|
socketEndpoints.set(socket, {
|
||||||
|
localAddress: socket.localAddress,
|
||||||
|
localPort: socket.localPort,
|
||||||
|
remoteAddress: socket.remoteAddress,
|
||||||
|
remotePort: socket.remotePort,
|
||||||
|
remoteFamily: socket.remoteFamily as "IPv4" | "IPv6",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.onClientError) {
|
||||||
|
server.on("clientError", (err, socket: Socket) => {
|
||||||
|
if (err["code"] !== "ECONNRESET" && socket.writable)
|
||||||
|
socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
|
||||||
|
|
||||||
|
// As per Node docs: This event is guaranteed to be passed an instance
|
||||||
|
// of the <net.Socket> class
|
||||||
|
options.onClientError(err, socket as Socket);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
server.timeout = options.timeout || 0;
|
server.timeout = options.timeout || 0;
|
||||||
if (options.keepAliveTimeout != null)
|
if (options.keepAliveTimeout != null)
|
||||||
server.keepAliveTimeout = options.keepAliveTimeout;
|
server.keepAliveTimeout = options.keepAliveTimeout;
|
||||||
@@ -99,3 +132,8 @@ export function stop(): Promise<void> {
|
|||||||
closeServer(20000, resolve);
|
closeServer(20000, resolve);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSocketEndpoints(socket: Socket): SocketEndpoint {
|
||||||
|
// TLSSocket keeps a reference to the raw TCP socket in _parent
|
||||||
|
return socketEndpoints.get(socket["_parent"] ?? socket);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user