274 lines
10 KiB
C
274 lines
10 KiB
C
/*
|
|
* TeamSpeak SDK client multi-connection sample
|
|
*
|
|
* Spawns multiple simultaneous server connection handlers from a single
|
|
* client application, all connecting to the same server. Intended to
|
|
* reproduce/diagnose the issue where secondary sessions are dropped after
|
|
* ~10 seconds with "10 resends of COMMAND packet" / ping timeout errors.
|
|
*
|
|
* Usage:
|
|
* ts_client_multi [host] [port] [serverPassword] [numConnections] [runSeconds] [staggerMs] [shareIdentity] [localPort]
|
|
*
|
|
* Defaults: localhost 9987 "secret" 3 60 0 0 0
|
|
*
|
|
* localPort: local UDP port passed to spawnNewServerConnectionHandler.
|
|
* 0 = ephemeral (a fresh port per connection handler). A fixed
|
|
* value makes all connection handlers request the SAME local port,
|
|
* mimicking applications that pin their source port.
|
|
*
|
|
* staggerMs: delay between issuing each connection (0 = all simultaneous).
|
|
* Use a value larger than the handshake time (e.g. 2000) to start
|
|
* each secondary session only after the previous one is fully
|
|
* established, matching the reported "secondary sessions initiated
|
|
* afterwards" scenario.
|
|
*
|
|
* Copyright (c) TeamSpeak-Systems
|
|
*/
|
|
|
|
#ifdef _WIN32
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#include <Windows.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#endif
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
|
|
#include <teamspeak/public_definitions.h>
|
|
#include <teamspeak/public_errors.h>
|
|
#include <teamspeak/clientlib.h>
|
|
|
|
#ifdef _WIN32
|
|
#define SLEEP(x) Sleep(x)
|
|
#define strdup(x) _strdup(x)
|
|
#else
|
|
#define SLEEP(x) usleep((x)*1000)
|
|
#endif
|
|
|
|
#define MAX_CONNECTIONS 32
|
|
|
|
/* Track the connection handlers we created so the status callback can label them. */
|
|
static uint64 g_handlers[MAX_CONNECTIONS];
|
|
static int g_numHandlers = 0;
|
|
static time_t g_startTime;
|
|
|
|
static int handlerIndex(uint64 scHandlerID) {
|
|
for (int i = 0; i < g_numHandlers; ++i) {
|
|
if (g_handlers[i] == scHandlerID) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static double elapsed(void) {
|
|
return difftime(time(NULL), g_startTime);
|
|
}
|
|
|
|
static const char* statusName(int status) {
|
|
switch (status) {
|
|
case STATUS_DISCONNECTED: return "DISCONNECTED";
|
|
case STATUS_CONNECTING: return "CONNECTING";
|
|
case STATUS_CONNECTED: return "CONNECTED";
|
|
case STATUS_CONNECTION_ESTABLISHING:return "CONNECTION_ESTABLISHING";
|
|
case STATUS_CONNECTION_ESTABLISHED: return "CONNECTION_ESTABLISHED";
|
|
default: return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Connection status change. This is where we expect to observe the secondary
|
|
* connections dropping back to DISCONNECTED with a non-zero error after ~10s.
|
|
*/
|
|
void onConnectStatusChangeEvent(uint64 serverConnectionHandlerID, int newStatus, unsigned int errorNumber) {
|
|
int idx = handlerIndex(serverConnectionHandlerID);
|
|
char* errormsg = NULL;
|
|
const char* errstr = "";
|
|
if (errorNumber != ERROR_ok && ts3client_getErrorMessage(errorNumber, &errormsg) == ERROR_ok)
|
|
errstr = errormsg;
|
|
|
|
printf("[%6.1fs] conn#%d (sch=%llu): %s (err=%u %s)\n",
|
|
elapsed(), idx, (unsigned long long)serverConnectionHandlerID,
|
|
statusName(newStatus), errorNumber, errstr);
|
|
fflush(stdout);
|
|
|
|
if (errormsg) ts3client_freeMemory(errormsg);
|
|
}
|
|
|
|
void onClientMoveTimeoutEvent(uint64 serverConnectionHandlerID, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility, const char* timeoutMessage) {
|
|
int idx = handlerIndex(serverConnectionHandlerID);
|
|
printf("[%6.1fs] conn#%d: clientID %u timed out: %s\n", elapsed(), idx, clientID, timeoutMessage ? timeoutMessage : "");
|
|
fflush(stdout);
|
|
}
|
|
|
|
void onServerErrorEvent(uint64 serverConnectionHandlerID, const char* errorMessage, unsigned int error, const char* returnCode, const char* extraMessage) {
|
|
int idx = handlerIndex(serverConnectionHandlerID);
|
|
printf("[%6.1fs] conn#%d: server error: %s %s\n", elapsed(), idx, errorMessage ? errorMessage : "", extraMessage ? extraMessage : "");
|
|
fflush(stdout);
|
|
}
|
|
|
|
char* programPath(char* programInvocation) {
|
|
char* path;
|
|
char* end;
|
|
int length;
|
|
char pathsep;
|
|
|
|
if (programInvocation == NULL) return strdup("");
|
|
|
|
#ifdef _WIN32
|
|
pathsep = '\\';
|
|
#else
|
|
pathsep = '/';
|
|
#endif
|
|
|
|
end = strrchr(programInvocation, pathsep);
|
|
if (!end) return strdup("");
|
|
|
|
length = (end - programInvocation) + 2;
|
|
path = (char*)malloc(length);
|
|
strncpy(path, programInvocation, length - 1);
|
|
path[length - 1] = 0;
|
|
|
|
return path;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
unsigned int error;
|
|
char* version;
|
|
char* path;
|
|
|
|
/* --- arguments --- */
|
|
const char* host = (argc > 1) ? argv[1] : "localhost";
|
|
unsigned int port = (argc > 2) ? (unsigned int)atoi(argv[2]) : 9987;
|
|
const char* serverPassword = (argc > 3) ? argv[3] : "secret";
|
|
int numConnections = (argc > 4) ? atoi(argv[4]) : 3;
|
|
int runSeconds = (argc > 5) ? atoi(argv[5]) : 60;
|
|
int staggerMs = (argc > 6) ? atoi(argv[6]) : 0;
|
|
int shareIdentity = (argc > 7) ? atoi(argv[7]) : 0;
|
|
int localPort = (argc > 8) ? atoi(argv[8]) : 0;
|
|
|
|
if (numConnections < 1) numConnections = 1;
|
|
if (numConnections > MAX_CONNECTIONS) numConnections = MAX_CONNECTIONS;
|
|
|
|
printf("Multi-connection test: host=%s port=%u connections=%d runSeconds=%d staggerMs=%d shareIdentity=%d localPort=%d\n",
|
|
host, port, numConnections, runSeconds, staggerMs, shareIdentity, localPort);
|
|
|
|
/* Create struct for callback function pointers */
|
|
struct ClientUIFunctions funcs;
|
|
memset(&funcs, 0, sizeof(struct ClientUIFunctions));
|
|
funcs.onConnectStatusChangeEvent = onConnectStatusChangeEvent;
|
|
funcs.onClientMoveTimeoutEvent = onClientMoveTimeoutEvent;
|
|
funcs.onServerErrorEvent = onServerErrorEvent;
|
|
|
|
/* Initialize client lib with callbacks. Resource path points to the dir
|
|
* containing the soundbackends (next to the executable). */
|
|
path = programPath(argv[0]);
|
|
error = ts3client_initClientLib(&funcs, NULL, LogType_FILE | LogType_CONSOLE, NULL, path);
|
|
free(path);
|
|
|
|
if (error != ERROR_ok) {
|
|
char* errormsg;
|
|
if (ts3client_getErrorMessage(error, &errormsg) == ERROR_ok) {
|
|
printf("Error initializing clientlib: %s\n", errormsg);
|
|
ts3client_freeMemory(errormsg);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if ((error = ts3client_getClientLibVersion(&version)) == ERROR_ok) {
|
|
printf("Client lib version: %s\n", version);
|
|
ts3client_freeMemory(version);
|
|
}
|
|
|
|
g_startTime = time(NULL);
|
|
|
|
/* When shareIdentity is set, all connections reuse a single identity (as a
|
|
* real application that stores and reuses one identity would). This mirrors
|
|
* "multiple sessions from the same client" and is a prime suspect for the
|
|
* handshake hardening rejecting secondary sessions from the same uid. */
|
|
char* sharedIdentity = NULL;
|
|
if (shareIdentity) {
|
|
if ((error = ts3client_createIdentity(&sharedIdentity)) != ERROR_ok) {
|
|
printf("Error creating shared identity: %u\n", error);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Spawn N connection handlers and connect each to the same server.
|
|
* No audio devices are opened: this keeps the test focused on the
|
|
* network/command layer and avoids capture-device contention. */
|
|
for (int i = 0; i < numConnections; ++i) {
|
|
uint64 scHandlerID;
|
|
char* identity = NULL;
|
|
char nickname[64];
|
|
|
|
if ((error = ts3client_spawnNewServerConnectionHandler(localPort, &scHandlerID)) != ERROR_ok) {
|
|
printf("Error spawning server connection handler #%d: %u\n", i, error);
|
|
continue;
|
|
}
|
|
g_handlers[g_numHandlers++] = scHandlerID;
|
|
|
|
if (shareIdentity) {
|
|
identity = sharedIdentity;
|
|
} else if ((error = ts3client_createIdentity(&identity)) != ERROR_ok) {
|
|
printf("Error creating identity #%d: %u\n", i, error);
|
|
continue;
|
|
}
|
|
|
|
snprintf(nickname, sizeof(nickname), "multi_%d", i);
|
|
|
|
if ((error = ts3client_startConnection(scHandlerID, identity, host, port, nickname, NULL, "", serverPassword)) != ERROR_ok) {
|
|
printf("Error connecting conn#%d to server: %u\n", i, error);
|
|
} else {
|
|
printf("[%6.1fs] conn#%d (sch=%llu): startConnection issued as \"%s\"%s\n",
|
|
elapsed(), i, (unsigned long long)scHandlerID, nickname,
|
|
shareIdentity ? " [shared identity]" : "");
|
|
}
|
|
if (!shareIdentity && identity)
|
|
ts3client_freeMemory(identity);
|
|
|
|
/* Optionally wait before starting the next connection, so secondary
|
|
* sessions are initiated only after the previous one is up. */
|
|
if (staggerMs > 0 && i + 1 < numConnections) {
|
|
SLEEP(staggerMs);
|
|
}
|
|
}
|
|
|
|
if (sharedIdentity) ts3client_freeMemory(sharedIdentity);
|
|
|
|
printf("\n--- %d connection(s) started. Observing for %d seconds. ---\n", g_numHandlers, runSeconds);
|
|
printf("--- Watch for secondary connections dropping (resend/ping timeout). ---\n\n");
|
|
fflush(stdout);
|
|
|
|
/* Idle loop, printing a periodic status snapshot so drops are easy to spot. */
|
|
for (int t = 0; t < runSeconds; ++t) {
|
|
SLEEP(1000);
|
|
if ((t + 1) % 5 == 0) {
|
|
printf("[%6.1fs] status snapshot:\n", elapsed());
|
|
for (int i = 0; i < g_numHandlers; ++i) {
|
|
int status = 0;
|
|
ts3client_getConnectionStatus(g_handlers[i], &status);
|
|
printf(" conn#%d (sch=%llu): %s\n", i, (unsigned long long)g_handlers[i], statusName(status));
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
/* Disconnect and clean up all connection handlers. */
|
|
printf("\nDisconnecting all connections...\n");
|
|
for (int i = 0; i < g_numHandlers; ++i) {
|
|
ts3client_stopConnection(g_handlers[i], "leaving");
|
|
}
|
|
SLEEP(500);
|
|
for (int i = 0; i < g_numHandlers; ++i) {
|
|
ts3client_destroyServerConnectionHandler(g_handlers[i]);
|
|
}
|
|
|
|
if ((error = ts3client_destroyClientLib()) != ERROR_ok) {
|
|
printf("Failed to destroy clientlib: %u\n", error);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|