/* * 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 #else #include #include #include #endif #include #include #include #include #include #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; }