mirror of
https://github.com/coder/code-server.git
synced 2026-02-05 06:23:35 -06:00
- It logs the error now. - For some reason when there is an error node-netstat runs the callback twice. That resulted in us scheduling an exponentially growing number of calls which ate up all the CPU (and probably memory eventually). For now, opted to dispose when there is an error.
109 lines
2.4 KiB
TypeScript
109 lines
2.4 KiB
TypeScript
//@ts-ignore
|
|
import * as netstat from "node-netstat";
|
|
import { Event, Emitter } from "@coder/events";
|
|
import { logger } from "@coder/logger";
|
|
|
|
export interface PortScanner {
|
|
readonly ports: ReadonlyArray<number>;
|
|
|
|
readonly onAdded: Event<ReadonlyArray<number>>;
|
|
readonly onRemoved: Event<ReadonlyArray<number>>;
|
|
|
|
dispose(): void;
|
|
}
|
|
|
|
/**
|
|
* Creates a disposable port scanner.
|
|
* Will scan local ports and emit events when ports are added or removed.
|
|
* Currently only scans TCP ports.
|
|
*/
|
|
export const createPortScanner = (scanInterval: number = 250): PortScanner => {
|
|
const ports = new Map<number, number>();
|
|
|
|
const addEmitter = new Emitter<number[]>();
|
|
const removeEmitter = new Emitter<number[]>();
|
|
|
|
const scan = (onCompleted: (err?: Error) => void): void => {
|
|
const scanTime = Date.now();
|
|
const added: number[] = [];
|
|
netstat({
|
|
done: (err: Error): void => {
|
|
const removed: number[] = [];
|
|
ports.forEach((value, key) => {
|
|
if (value !== scanTime) {
|
|
// Remove port
|
|
removed.push(key);
|
|
ports.delete(key);
|
|
}
|
|
});
|
|
if (removed.length > 0) {
|
|
removeEmitter.emit(removed);
|
|
}
|
|
|
|
if (added.length > 0) {
|
|
addEmitter.emit(added);
|
|
}
|
|
|
|
onCompleted(err);
|
|
},
|
|
filter: {
|
|
state: "LISTEN",
|
|
},
|
|
}, (data: {
|
|
readonly protocol: string;
|
|
readonly local: {
|
|
readonly port: number;
|
|
readonly address: string;
|
|
};
|
|
}) => {
|
|
// https://en.wikipedia.org/wiki/Registered_port
|
|
if (data.local.port <= 1023 || data.local.port >= 49151) {
|
|
return;
|
|
}
|
|
// Only forward TCP ports
|
|
if (!data.protocol.startsWith("tcp")) {
|
|
return;
|
|
}
|
|
|
|
if (!ports.has(data.local.port)) {
|
|
added.push(data.local.port);
|
|
}
|
|
ports.set(data.local.port, scanTime);
|
|
});
|
|
};
|
|
|
|
let lastTimeout: NodeJS.Timer | undefined;
|
|
let disposed: boolean = false;
|
|
|
|
const doInterval = (): void => {
|
|
scan((error) => {
|
|
if (error) {
|
|
logger.error(`Port scanning will not be available: ${error.message}.`);
|
|
disposed = true;
|
|
} else if (!disposed) {
|
|
lastTimeout = setTimeout(doInterval, scanInterval);
|
|
}
|
|
});
|
|
};
|
|
|
|
doInterval();
|
|
|
|
return {
|
|
get ports(): number[] {
|
|
return Array.from(ports.keys());
|
|
},
|
|
get onAdded(): Event<number[]> {
|
|
return addEmitter.event;
|
|
},
|
|
get onRemoved(): Event<number[]> {
|
|
return removeEmitter.event;
|
|
},
|
|
dispose(): void {
|
|
if (typeof lastTimeout !== "undefined") {
|
|
clearTimeout(lastTimeout);
|
|
}
|
|
disposed = true;
|
|
},
|
|
};
|
|
};
|