Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/dimensions/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ class Client {
return this.player.name;
}

public getTrackedPlayerCount(): number {
return Object.keys(this.globalTracking.names).length;
}

public handleDataSend(encodedData: Buffer): void {
try {
// Add Buffer Packet (incomplete packet from last data)
Expand Down
6 changes: 1 addition & 5 deletions app/dimensions/clientcommandhandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,7 @@ export class ClientCommandHandler {
* @return Whether or not the who command was handled
*/
private handleWho(client: Client): boolean {
let total: number = 0;
let keys: string[] = Object.keys(client.serversDetails);
for (let i: number = 0, len = keys.length; i < len; i++) {
total += client.serversDetails[keys[i]].clientCount;
}
const total = client.getTrackedPlayerCount();

// Try to make it come after the normal response
setTimeout(function() {
Expand Down
28 changes: 19 additions & 9 deletions app/dimensions/listenserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,19 @@ export class ListenServer {
* @param socket The socket of a new client
*/
private async handleSocket(socket: Net.Socket): Promise<void> {
if ((this.options.connectionLimit.enabled && this.enforceConnectionLimit(socket))
|| this.options.connectionRateLimit.enabled && this.enforceConnectionRateLimit(socket)) {
const socketIp = socket.remoteAddress;

if (this.options.connectionLimit.enabled && this.enforceConnectionLimit(socket)) {
socket.removeAllListeners();
return;
}

if (this.options.connectionRateLimit.enabled && this.enforceConnectionRateLimit(socket)) {
// If connectionLimit accepted this socket first, rollback its tracker entry
// when rate limit later rejects the same connection.
if (this.options.connectionLimit.enabled && typeof socketIp !== "undefined") {
this.decrementConnectionTracker(socketIp);
}
socket.removeAllListeners();
return;
}
Expand Down Expand Up @@ -400,13 +411,13 @@ export class ListenServer {
* the socket. Also checking if the ip address of this socket is blacklisted.
*/
private async setupNewSocket(socket: Net.Socket): Promise<void> {
const socketIp = socket.remoteAddress;
let chosenServer: RoutingServer | null = this.chooseServer();
if (chosenServer === null) {
this.logging.warn(`No servers available for ListenServer[Port: ${this.port}]`);
socket.destroy();
const ip = socket.remoteAddress;
if (typeof ip !== "undefined") {
this.decrementConnectionTracker(ip);
if (typeof socketIp !== "undefined") {
this.decrementConnectionTracker(socketIp);
}
return;
}
Expand Down Expand Up @@ -482,9 +493,8 @@ export class ListenServer {
if (index > -1) {
this.checkingClients.splice(index, 1);
}
const ip = clientArgs.socket.remoteAddress;
if (typeof ip !== "undefined") {
this.decrementConnectionTracker(ip);
if (typeof socketIp !== "undefined") {
this.decrementConnectionTracker(socketIp);
}
}
});
Expand Down Expand Up @@ -671,7 +681,7 @@ export class ListenServer {
}

try {
const ip = socket.remoteAddress;
const ip = client.ip || socket.remoteAddress;
if (typeof ip !== "undefined") {
this.decrementConnectionTracker(ip);
}
Expand Down
22 changes: 20 additions & 2 deletions app/spec/dimensions/clientcommandhandlerspec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ describe("ClientCommandHandler", () => {
let serverB: RoutingServer;
let socket: Net.Socket;
let tcpServer: Net.Server;
let serversDetails;
let serversDetails: any;
let globalHandlers;
let servers;
let globalTracking;
let globalTracking: any;
let client: Client;
// @ts-ignore
let server: TerrariaServer;
Expand Down Expand Up @@ -204,6 +204,24 @@ describe("ClientCommandHandler", () => {
expect(handled).toBe(false);
});

it("should use tracked player names for who count", (done: DoneFn) => {
serversDetails.servera.clientCount = 8;
serversDetails.serverb.clientCount = 9;
globalTracking.names = { one: true, two: true };

const handler = (data: string) => {
if (data.indexOf("There are 2 players across all Dimensions") === -1) {
return;
}
clientSocketDataHandlers = clientSocketDataHandlers.filter(h => h !== handler);
done();
};
clientSocketDataHandlers.push(handler);

let command = client.globalHandlers.command.parseCommand("/who");
client.globalHandlers.command.handle(command, client);
});

it("should send the user a user count", (done: DoneFn) => {
const handler = (data: string) => {
if (data.indexOf("There are 0 players across all Dimensions") === -1) {
Expand Down
138 changes: 138 additions & 0 deletions app/spec/dimensions/listenserverspec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import * as Net from 'net';
import * as winston from 'winston';
import ListenServer from '../../dimensions/listenserver.js';
import ListenServerArgs from '../../dimensions/listenserverargs.js';
import RoutingServer from '../../dimensions/routingserver.js';
import ClientCommandHandler from '../../dimensions/clientcommandhandler.js';
import ClientPacketHandler from '../../dimensions/clientpackethandler.js';
import TerrariaServerPacketHandler from '../../dimensions/terrariaserverpackethandler.js';
import { ConfigOptions } from '../../dimensions/configloader.js';
import * as Language from '../../dimensions/language.js';

describe("ListenServer", () => {
let listenServer!: ListenServer;
let connectionsTracker!: Map<string, number>;
let connectRateTracker!: Map<string, number>;

beforeEach(() => {
connectionsTracker = new Map();
connectRateTracker = new Map();

const routingServer: RoutingServer = {
name: "servera",
serverIP: "127.0.0.1",
serverPort: 7777,
hidden: false,
isVanilla: false,
};

const options: ConfigOptions = {
socketTimeout: 0,
socketNoDelay: true,
fakeVersion: {
enabled: false,
terrariaVersion: 0
},
blacklist: {
enabled: false,
},
blockInvis: false,
log: {
clientBlocked: false,
clientConnect: false,
clientDisconnect: false,
clientError: false,
clientTimeouts: false,
checkingClientConnect: false,
checkingClientDisconnect: false,
checkingClientError: false,
checkingClientTimeouts: false,
extensionLoad: false,
outputToFile: false,
outputToConsole: false,
tServerConnect: false,
tServerDisconnect: false,
tServerError: false,
extensionError: false,
},
restApi: {
enabled: false,
port: 0
},
connectionLimit: {
enabled: true,
connectionLimitPerIP: 2,
kickReason: ""
},
connectionRateLimit: {
enabled: true,
connectionRateLimitPerIP: 1
},
redis: {
enabled: false,
host: "localhost",
port: 6379
},
language: Language.english,
debuffOnSwitch: { enabled: false },
disconnectOnKick: { type: "never" },
hotReload: false
};

const args: ListenServerArgs = {
info: {
listenPort: 0,
routingServers: [routingServer]
},
serversDetails: {
servera: {
clientCount: 0,
disabled: false,
disabledTimeout: null,
failedConnAttempts: 0
}
},
globalHandlers: {
command: new ClientCommandHandler(),
clientPacketHandler: new ClientPacketHandler(),
terrariaServerPacketHandler: new TerrariaServerPacketHandler(),
extensions: {}
},
servers: {
servera: routingServer
},
options,
globalTracking: {
names: {}
},
logging: winston.createLogger({ silent: true }),
connectionsTracker,
connectRateTracker
};

listenServer = new ListenServer(args);
});

afterEach(() => {
listenServer.shutdown();
});

it("should rollback the connection tracker when rate limit rejects a socket", async () => {
const ip = "127.0.0.1";
connectRateTracker.set(ip, 1);

const removeAllListeners = jasmine.createSpy("removeAllListeners");
const destroy = jasmine.createSpy("destroy");
const socket = {
remoteAddress: ip,
removeAllListeners,
destroy
} as unknown as Net.Socket;

await (listenServer as any).handleSocket(socket);

expect(connectionsTracker.has(ip)).toBe(false);
expect(removeAllListeners).toHaveBeenCalled();
expect(destroy).toHaveBeenCalled();
});
});