|
|
|
@ -79,12 +79,19 @@ function extractSNI(buffer: Buffer): string | undefined {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface IConnectionRecord {
|
|
|
|
|
incoming: plugins.net.Socket;
|
|
|
|
|
outgoing: plugins.net.Socket | null;
|
|
|
|
|
incomingStartTime: number;
|
|
|
|
|
outgoingStartTime?: number;
|
|
|
|
|
connectionClosed: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class PortProxy {
|
|
|
|
|
netServer: plugins.net.Server;
|
|
|
|
|
settings: IProxySettings;
|
|
|
|
|
private activeConnections: Set<plugins.net.Socket> = new Set();
|
|
|
|
|
private incomingConnectionTimes: Map<plugins.net.Socket, number> = new Map();
|
|
|
|
|
private outgoingConnectionTimes: Map<plugins.net.Socket, number> = new Map();
|
|
|
|
|
// Unified record tracking each connection pair.
|
|
|
|
|
private connectionRecords: Set<IConnectionRecord> = new Set();
|
|
|
|
|
private connectionLogger: NodeJS.Timeout | null = null;
|
|
|
|
|
|
|
|
|
|
private terminationStats: {
|
|
|
|
@ -140,29 +147,26 @@ export class PortProxy {
|
|
|
|
|
|
|
|
|
|
this.netServer = plugins.net.createServer((socket: plugins.net.Socket) => {
|
|
|
|
|
const remoteIP = socket.remoteAddress || '';
|
|
|
|
|
this.activeConnections.add(socket);
|
|
|
|
|
this.incomingConnectionTimes.set(socket, Date.now());
|
|
|
|
|
console.log(`New connection from ${remoteIP}. Active connections: ${this.activeConnections.size}`);
|
|
|
|
|
const connectionRecord: IConnectionRecord = {
|
|
|
|
|
incoming: socket,
|
|
|
|
|
outgoing: null,
|
|
|
|
|
incomingStartTime: Date.now(),
|
|
|
|
|
connectionClosed: false,
|
|
|
|
|
};
|
|
|
|
|
this.connectionRecords.add(connectionRecord);
|
|
|
|
|
console.log(`New connection from ${remoteIP}. Active connections: ${this.connectionRecords.size}`);
|
|
|
|
|
|
|
|
|
|
let initialDataReceived = false;
|
|
|
|
|
let incomingTerminationReason: string | null = null;
|
|
|
|
|
let outgoingTerminationReason: string | null = null;
|
|
|
|
|
let targetSocket: plugins.net.Socket | null = null;
|
|
|
|
|
let connectionClosed = false;
|
|
|
|
|
|
|
|
|
|
// Ensure cleanup happens only once.
|
|
|
|
|
// Ensure cleanup happens only once for the entire connection record.
|
|
|
|
|
const cleanupOnce = () => {
|
|
|
|
|
if (!connectionClosed) {
|
|
|
|
|
connectionClosed = true;
|
|
|
|
|
cleanUpSockets(socket, targetSocket || undefined);
|
|
|
|
|
this.incomingConnectionTimes.delete(socket);
|
|
|
|
|
if (targetSocket) {
|
|
|
|
|
this.outgoingConnectionTimes.delete(targetSocket);
|
|
|
|
|
}
|
|
|
|
|
if (this.activeConnections.has(socket)) {
|
|
|
|
|
this.activeConnections.delete(socket);
|
|
|
|
|
console.log(`Connection from ${remoteIP} terminated. Active connections: ${this.activeConnections.size}`);
|
|
|
|
|
}
|
|
|
|
|
if (!connectionRecord.connectionClosed) {
|
|
|
|
|
connectionRecord.connectionClosed = true;
|
|
|
|
|
cleanUpSockets(connectionRecord.incoming, connectionRecord.outgoing || undefined);
|
|
|
|
|
this.connectionRecords.delete(connectionRecord);
|
|
|
|
|
console.log(`Connection from ${remoteIP} terminated. Active connections: ${this.connectionRecords.size}`);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -242,10 +246,10 @@ export class PortProxy {
|
|
|
|
|
connectionOptions.localAddress = remoteIP.replace('::ffff:', '');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
targetSocket = plugins.net.connect(connectionOptions);
|
|
|
|
|
if (targetSocket) {
|
|
|
|
|
this.outgoingConnectionTimes.set(targetSocket, Date.now());
|
|
|
|
|
}
|
|
|
|
|
const targetSocket = plugins.net.connect(connectionOptions);
|
|
|
|
|
connectionRecord.outgoing = targetSocket;
|
|
|
|
|
connectionRecord.outgoingStartTime = Date.now();
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
`Connection established: ${remoteIP} -> ${targetHost}:${this.settings.toPort}` +
|
|
|
|
|
`${serverName ? ` (SNI: ${serverName})` : ''}`
|
|
|
|
@ -314,20 +318,19 @@ export class PortProxy {
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Log active connection count, longest running connection durations,
|
|
|
|
|
// and termination statistics every 10 seconds.
|
|
|
|
|
// Every 10 seconds log active connection count and longest running durations.
|
|
|
|
|
this.connectionLogger = setInterval(() => {
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
let maxIncoming = 0;
|
|
|
|
|
for (const startTime of this.incomingConnectionTimes.values()) {
|
|
|
|
|
maxIncoming = Math.max(maxIncoming, now - startTime);
|
|
|
|
|
}
|
|
|
|
|
let maxOutgoing = 0;
|
|
|
|
|
for (const startTime of this.outgoingConnectionTimes.values()) {
|
|
|
|
|
maxOutgoing = Math.max(maxOutgoing, now - startTime);
|
|
|
|
|
for (const record of this.connectionRecords) {
|
|
|
|
|
maxIncoming = Math.max(maxIncoming, now - record.incomingStartTime);
|
|
|
|
|
if (record.outgoingStartTime) {
|
|
|
|
|
maxOutgoing = Math.max(maxOutgoing, now - record.outgoingStartTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
console.log(
|
|
|
|
|
`(Interval Log) Active connections: ${this.activeConnections.size}. ` +
|
|
|
|
|
`(Interval Log) Active connections: ${this.connectionRecords.size}. ` +
|
|
|
|
|
`Longest running incoming: ${plugins.prettyMs(maxIncoming)}, outgoing: ${plugins.prettyMs(maxOutgoing)}. ` +
|
|
|
|
|
`Termination stats (incoming): ${JSON.stringify(this.terminationStats.incoming)}, ` +
|
|
|
|
|
`(outgoing): ${JSON.stringify(this.terminationStats.outgoing)}`
|
|
|
|
|