Skip to content

Commit 5e6982c

Browse files
Fix for more resilient WS check + Subprotocol inputs + Timeouts (#6551)
2 parents 4a27f92 + bcdf6b8 commit 5e6982c

4 files changed

Lines changed: 267 additions & 127 deletions

File tree

server/monitor-types/websocket-upgrade.js

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
const { MonitorType } = require("./monitor-type");
22
const WebSocket = require("ws");
33
const { UP } = require("../../src/util");
4+
const { checkStatusCode } = require("../util-server");
5+
// Define closing error codes https://www.iana.org/assignments/websocket/websocket.xml#close-code-number
6+
const WS_ERR_CODE = {
7+
1002: "Protocol error",
8+
1003: "Unsupported Data",
9+
1005: "No Status Received",
10+
1006: "Abnormal Closure",
11+
1007: "Invalid frame payload data",
12+
1008: "Policy Violation",
13+
1009: "Message Too Big",
14+
1010: "Mandatory Extension Missing",
15+
1011: "Internal Error",
16+
1012: "Service Restart",
17+
1013: "Try Again Later",
18+
1014: "Bad Gateway",
19+
1015: "TLS Handshake Failed",
20+
3000: "Unauthorized",
21+
3003: "Forbidden",
22+
3008: "Timeout",
23+
};
424

525
class WebSocketMonitorType extends MonitorType {
626
name = "websocket-upgrade";
@@ -11,43 +31,56 @@ class WebSocketMonitorType extends MonitorType {
1131
async check(monitor, heartbeat, _server) {
1232
const [ message, code ] = await this.attemptUpgrade(monitor);
1333

14-
if (code === 1000) {
15-
heartbeat.status = UP;
16-
heartbeat.msg = message;
17-
} else {
18-
throw new Error(message);
34+
if (typeof code !== "undefined") {
35+
// If returned status code matches user controlled accepted status code(default 1000), return success
36+
if (checkStatusCode(code, JSON.parse(monitor.accepted_statuscodes_json))) {
37+
heartbeat.status = UP;
38+
heartbeat.msg = message;
39+
return; // success at this point
40+
}
41+
42+
// Throw an error using friendly name if defined, fallback to generic msg
43+
throw new Error(WS_ERR_CODE[code] || `Unexpected status code: ${code}`);
44+
}
45+
// If no close code, then an error has occurred, display to user
46+
if (typeof message !== "undefined") {
47+
throw new Error(`${message}`);
1948
}
49+
// Throw generic error if nothing is defined, should never happen
50+
throw new Error("Unknown Websocket Error");
2051
}
2152

2253
/**
23-
* Uses the builtin Websocket API to establish a connection to target server
54+
* Uses the ws Node.js library to establish a connection to target server
2455
* @param {object} monitor The monitor object for input parameters.
2556
* @returns {[ string, int ]} Array containing a status message and response code
2657
*/
2758
async attemptUpgrade(monitor) {
2859
return new Promise((resolve) => {
29-
let ws;
30-
//If user selected a subprotocol, sets Sec-WebSocket-Protocol header. Subprotocol Identifier column: https://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name
31-
ws = monitor.wsSubprotocol === "" ? new WebSocket(monitor.url) : new WebSocket(monitor.url, monitor.wsSubprotocol);
60+
const timeoutMs = (monitor.timeout ?? 20) * 1000;
61+
// If user inputs subprotocol(s), convert to array, set Sec-WebSocket-Protocol header, timeout in ms. Subprotocol Identifier column: https://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name
62+
const subprotocol = monitor.wsSubprotocol ? monitor.wsSubprotocol.replace(/\s/g, "").split(",") : undefined;
63+
const ws = new WebSocket(monitor.url, subprotocol, { handshakeTimeout: timeoutMs });
3264

3365
ws.addEventListener("open", (event) => {
3466
// Immediately close the connection
3567
ws.close(1000);
3668
});
3769

3870
ws.onerror = (error) => {
39-
// Give user the choice to ignore Sec-WebSocket-Accept header
71+
// Give user the choice to ignore Sec-WebSocket-Accept header for non compliant servers
72+
// Header in HTTP 101 Switching Protocols response from server, technically already upgraded to WS
4073
if (monitor.wsIgnoreSecWebsocketAcceptHeader && error.message === "Invalid Sec-WebSocket-Accept header") {
41-
resolve([ "101 - OK", 1000 ]);
74+
resolve([ "1000 - OK", 1000 ]);
4275
return;
4376
}
4477
// Upgrade failed, return message to user
4578
resolve([ error.message, error.code ]);
4679
};
4780

4881
ws.onclose = (event) => {
49-
// Upgrade success, connection closed successfully
50-
resolve([ "101 - OK", event.code ]);
82+
// Return the close code, if connection didn't close cleanly, return the reason if present
83+
resolve([ event.wasClean ? event.code.toString() + " - OK" : event.reason, event.code ]);
5184
};
5285
});
5386
}

src/lang/en.json

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -90,39 +90,9 @@
9090
"upsideDownModeDescription": "Flip the status upside down. If the service is reachable, it is DOWN.",
9191
"ignoreSecWebsocketAcceptHeaderDescription": "Allows the server to not reply with Sec-WebSocket-Accept header, if the websocket upgrade succeeds.",
9292
"Ignore Sec-WebSocket-Accept header": "Ignore {0} header",
93-
"wsSubprotocolDescription": "For more information on subprotocols, please consult the {documentation}",
94-
"WebSocket Application Messaging Protocol": "WAMP (The WebSocket Application Messaging Protocol)",
95-
"Session Initiation Protocol": "WebSocket Transport for SIP (Session Initiation Protocol)",
96-
"Subprotocol": "Subprotocol",
97-
"Network API for Notification Channel": "OMA RESTful Network API for Notification Channel",
98-
"Web Process Control Protocol": "Web Process Control Protocol (WPCP)",
99-
"Advanced Message Queuing Protocol": "Advanced Message Queuing Protocol (AMQP) 1.0+",
100-
"jsflow": "jsFlow pubsub/queue Protocol",
101-
"Reverse Web Process Control": "Reverse Web Process Control Protocol (RWPCP)",
102-
"Extensible Messaging and Presence Protocol": "WebSocket Transport for the Extensible Messaging and Presence Protocol (XMPP)",
103-
"Smart Home IP": "SHIP - Smart Home IP",
104-
"Miele Cloud Connect Protocol": "Miele Cloud Connect Protocol",
105-
"Push Channel Protocol": "Push Channel Protocol",
106-
"Message Session Relay Protocol": "WebSocket Transport for MSRP (Message Session Relay Protocol)",
107-
"Binary Floor Control Protocol": "WebSocket Transport for BFCP (Binary Floor Control Protocol)",
108-
"Softvelum Low Delay Protocol": "Softvelum Low Delay Protocol",
109-
"OPC UA Connection Protocol": "OPC UA Connection Protocol",
110-
"OPC UA JSON Encoding": "OPC UA JSON Encoding",
111-
"Swindon Web Server Protocol": "Swindon Web Server Protocol (JSON encoding)",
112-
"Broadband Forum User Services Platform": "USP (Broadband Forum User Services Platform)",
113-
"Constrained Application Protocol": "Constrained Application Protocol (CoAP)",
114-
"Softvelum WebSocket signaling protocol": "Softvelum WebSocket Signaling Protocol",
115-
"Cobra Real Time Messaging Protocol": "Cobra Real Time Messaging Protocol",
116-
"Declarative Resource Protocol": "Declarative Resource Protocol",
117-
"BACnet Secure Connect Hub Connection": "BACnet Secure Connect Hub Connection",
118-
"BACnet Secure Connect Direct Connection": "BACnet Secure Connect Direct Connection",
119-
"WebSocket Transport for JMAP": "WebSocket Transport for JMAP (JSON Meta Application Protocol)",
120-
"ITU-T T.140 Real-Time Text": "ITU-T T.140 Real-Time Text",
121-
"Done.best IoT Protocol": "Done.best IoT Protocol",
122-
"Collection Update": "The Collection Update Websocket Subprotocol",
123-
"Text IRC Protocol": "Text IRC Protocol",
124-
"Binary IRC Protocol": "Binary IRC Protocol",
125-
"Penguin Statistics Live Protocol v3": "Penguin Statistics Live Protocol v3 (Protobuf encoding)",
93+
"wsSubprotocolDescription": "Enter a comma delimited list of subprotocols. For more information on subprotocols, please consult the {documentation}",
94+
"wsCodeDescription": "For more information on status codes, please consult {rfc6455}",
95+
"Subprotocol(s)": "Subprotocol(s)",
12696
"maxRedirectDescription": "Maximum number of redirects to follow. Set to 0 to disable redirects.",
12797
"Upside Down Mode": "Upside Down Mode",
12898
"Max. Redirects": "Max. Redirects",

0 commit comments

Comments
 (0)