1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Deals with remote perf testing on customer machines
//
// $Workfile: $
// $NoKeywords: $
//===========================================================================//
#include "server_pch.h"
#include "cl_rcon.h"
#include "cl_steamauth.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifdef ENABLE_RPT
//-----------------------------------------------------------------------------
// Remote perf testing
//
// Description: Here's how rpt works. The 'customer' machine is the one
// we want to do testing on. The 'valve' machine is the programmer machine
// that has rcon access to the customer machine. The 'server' machine is the
// machine running a dedicated or listen server which both the customer + valve
// machines are connected to.
//
// Step 0) Customer runs with -rpt on the commandline, which turns on -condebug,
// clears out the console.log, and potentially other things.
//
// Step 1) Customer machine types rpt_password <password>. We store the password
// on the customer machine only and never forward it to the server to avoid
// having to worry about malicious servers. The password is stored in the RPTServer
// password The customer machine then forwards
// a command "rpt_client_enable 1" to the server indicating that it is a client
// which can be controlled via rpt.
//
// Step 2) Server machine receives the "rpt_client_enable 1" command. It stores
// some state indicating the client it received that message from can be rpt controlled.
//
// Step 3) Valve machine types rpt_start <password> [<port #>]. The port specification
// is optional. The valve machine's rpt rcon client puts itself into a listening
// mode, waiting for connection requests from the customer machine. Once
// connected, the RPTClient rcon client will be the mechanism by which the valve
// machine sends rcon commands to the customer machine. After this is done,
// the Valve machine sends a command "rpt_server_enable 1" to the server, indicating
// it wishes to connect to an rpt_client.
//
// Step 4) Server machine receives the 'rpt_server_enable 1' command. This
// command can only be received from IP addresses we know are at Valve,
// and from steam clients in the Valve group. This will turn cheats on on the
// server and will forward a message to the customer machine 'rpt_connect <valve ip address>'.
// It will only try to send this message to a client which has previously
// enabled rpt with the 'rpt_client_enable 1' command.
//
// Step 5) Customer machine receives the 'rpt_connect' command. This command
// can only work if the ip address specified comes from IP addresses we know
// are at Valve. The customer machine then attempts to connect to the RPTClient
// on the Valve machine, which establishes the connection to the customer
// RPTServer. The rcon classes will handle password authentication.
//
// Step 6) Valve machine types rpt <console commands> which are then sent
// to the customer machine through the custom tcp connection set up by the previous steps
//
// Step 7) Customer types 'rpt_password' with no password, which sends the
// message to the server 'rpt_client_enable 0'.
//
// Step 8) Server receives 'rpt_client_enable 0', which marks it as not
// willing to accept rpt connections. Future attempts to connect via
// 'rpt_server_enable 1' will fail.
//
// Step 9) Valve machine types rpt_end, which sends a command 'rpt_server_enable 0'
// to the server, which deactivates cheats on the server. It also disconnects
// the tcp connection to the customer machine.
//-----------------------------------------------------------------------------
static int g_nRptClientSlot = -1;
static int g_nRptServerSlot = -1;
// NOTE: This is expected to be executed by the customer, using rpt_password <password>
// This data will be stored in the client only. Server doesn't get access to the password
// to prevent malicious servers from running nasty commands on clients
#ifndef SWDS
CON_COMMAND_F( rpt_password, "", FCVAR_DONTRECORD | FCVAR_HIDDEN )
{
if ( CommandLine()->FindParm( "-rpt" ) == 0 )
{
ConMsg( "This command will not work unless the game is launched with -rpt\n" );
return;
}
if ( args.ArgC() > 2 )
{
ConMsg( "Incorrect # of arguments.\n" );
return;
}
// Doesn't work on dedicated servers
if ( NET_IsDedicated() || !NET_IsMultiplayer() || ( args.ArgC() > 2 ) )
{
ConMsg( "Failed!\n" );
return;
}
bool bWasEnabled = RPTServer().HasPassword();
char buf[255];
if ( args.ArgC() == 1 )
{
if ( !bWasEnabled )
return;
RPTServer().SetPassword( NULL );
ConMsg( "Disabling...\n" );
Q_snprintf( buf, sizeof( buf ), "rpt_client_enable 0" );
}
else if ( args.ArgC() == 2 )
{
RPTServer().SetPassword( args.Arg( 1 ) );
ConMsg( "New password : %s\n", args.Arg( 1 ) );
if ( bWasEnabled )
return;
ConMsg( "Enabling...\n" );
Q_snprintf( buf, sizeof( buf ), "rpt_client_enable 1" );
}
// Send a command to the server indicating this client can be rpted
CCommand argsClient;
argsClient.Tokenize( buf );
Cmd_ForwardToServer( argsClient, true );
}
#endif
// NOTE: This is autogenerated by clients typing rpt_password. The client keeps the
// password, but tells the server it can be rpted. Only 1 client can be rpted at a time.
CON_COMMAND_F( rpt_client_enable, "", FCVAR_DONTRECORD | FCVAR_HIDDEN | FCVAR_CLIENTCMD_CAN_EXECUTE )
{
if ( args.ArgC() != 2 )
return;
if ( cmd_clientslot < 0 )
return;
CGameClient *pClient = sv.Client( cmd_clientslot );
if ( !pClient )
return;
bool bEnable = ( atoi( args.Arg( 1 ) ) != 0 );
g_nRptClientSlot = bEnable ? cmd_clientslot : -1;
}
void SV_NotifyRPTOfDisconnect( int nClientSlot )
{
if ( nClientSlot == g_nRptClientSlot )
{
g_nRptClientSlot = -1;
}
if ( nClientSlot == g_nRptServerSlot )
{
g_nRptServerSlot = -1;
}
RPTServer().SetPassword( NULL );
#ifndef SWDS
RPTClient().SetPassword( NULL );
RPTClient().CloseListenSocket( );
#endif
}
#ifndef SWDS
void CL_NotifyRPTOfDisconnect( )
{
RPTServer().SetPassword( NULL );
RPTClient().SetPassword( NULL );
RPTClient().CloseListenSocket( );
}
#endif
#ifndef SWDS
// This runs on the valve client machine
CON_COMMAND_F( rpt_start, "", FCVAR_DONTRECORD | FCVAR_HIDDEN )
{
if ( args.ArgC() < 2 || args.ArgC() > 3 )
{
ConMsg( "Incorrect # of arguments.\n" );
return;
}
// Listen for connections from the customer machine
netadr_t rptAddr = net_local_adr;
if ( args.ArgC() == 3 )
{
rptAddr.SetPort( atoi( args.Arg( 2 ) ) );
}
else
{
rptAddr.SetPort( PORT_RPT_LISTEN );
}
RPTClient().SetPassword( args.Arg( 1 ) );
RPTClient().CreateListenSocket( rptAddr );
char pDir[MAX_PATH];
#ifdef WIN32
int nDay, nMonth, nYear;
GetCurrentDate( &nDay, &nMonth, &nYear );
Q_snprintf( pDir, sizeof(pDir), "rpt/%d_%d_%d", nMonth, nDay, nYear );
#elif POSIX
time_t now = time(NULL);
struct tm *tm = localtime( &now );
Q_snprintf( pDir, sizeof(pDir), "rpt/%d_%d_%d", tm->tm_mon, tm->tm_wday, tm->tm_year + 1900 );
#else
#error
#endif
RPTClient().SetRemoteFileDirectory( pDir );
// Send a command to the server indicating we want to connect to a remote client
char pBuf[256];
Q_snprintf( pBuf, sizeof(pBuf), "rpt_server_enable 1 %s\n", rptAddr.ToString() );
CCommand argsClient;
argsClient.Tokenize( pBuf );
Cmd_ForwardToServer( argsClient, true );
}
CON_COMMAND_F( rpt_end, "", FCVAR_DONTRECORD | FCVAR_HIDDEN )
{
if ( args.ArgC() != 1 )
{
ConMsg( "Incorrect # of arguments.\n" );
return;
}
RPTClient().SetPassword( NULL );
RPTClient().CloseListenSocket( );
// Send a command to the server indicating we want to disconnect from a remote client
char pBuf[256];
Q_snprintf( pBuf, sizeof(pBuf), "rpt_server_enable 0\n" );
CCommand argsClient;
argsClient.Tokenize( pBuf );
Cmd_ForwardToServer( argsClient, true );
}
#endif
// This is the steam id for user 'remote_perf_test'. See wiki for password.
// http://intranet.valvesoftware.com/wiki/index.php/Debugging_problems_on_customer_machines
static uint64 s_ValveMask = 0xFAB2423BFFA352AFull;
static uint64 s_pValveIDs[] =
{
76561197995463203ll ^ s_ValveMask,
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static bool IsValveIPAddress( const netadr_t &adr )
{
// Only accept this from clients inside of valve
netadr_t valveIP1, valveIP2;
valveIP1.SetIP( 0xCF, 0xAD, 0xB2, 0xFF );
valveIP2.SetIP( 0xCF, 0xAD, 0xB3, 0xFF );
return ( adr.CompareClassCAdr( valveIP1 ) || adr.CompareClassCAdr( valveIP2 ) || adr.IsLoopback() );
}
static bool PlayerIsValveEmployee( int nClientSlot )
{
CGameClient *pClient = sv.Client( nClientSlot );
if ( !pClient )
return false;
const netadr_t& adr = pClient->m_NetChannel->GetRemoteAddress();
if ( !IsValveIPAddress( adr ) )
return false;
// If Steam is running and connected to beta, player is valve
if ( k_EUniverseBeta == GetSteamUniverse() )
return true;
if ( !pClient->IsFullyAuthenticated() )
return false;
player_info_t pi;
if ( !sv.GetPlayerInfo( nClientSlot, &pi ) )
return false;
if ( !pi.friendsID )
return false;
CSteamID steamIDForPlayer( pi.friendsID, 1, k_EUniversePublic, k_EAccountTypeIndividual );
for ( int i = 0; i < ARRAYSIZE(s_pValveIDs); i++ )
{
if ( steamIDForPlayer.ConvertToUint64() == (s_pValveIDs[i] ^ s_ValveMask) )
return true;
}
return false;
}
// NOTE: This is expected to be called on the server as a result of rpt running
CON_COMMAND_F( rpt_server_enable, "", FCVAR_DONTRECORD | FCVAR_HIDDEN | FCVAR_CLIENTCMD_CAN_EXECUTE )
{
if ( args.ArgC() != 5 && args.ArgC() != 2 )
return;
if ( cmd_clientslot < 0 )
return;
if ( g_nRptServerSlot >= 0 && g_nRptServerSlot != cmd_clientslot )
return;
if ( !PlayerIsValveEmployee( cmd_clientslot ) )
return;
if ( g_nRptClientSlot < 0 )
{
ConMsg( "No valid clients.\n" );
g_nRptServerSlot = -1;
return;
}
bool bEnable = atoi( args.Arg( 1 ) ) != 0;
if ( !bEnable )
{
g_nRptServerSlot = -1;
return;
}
// Tell the customer machine to connect to the ip specified if the password matches
CGameClient *pClient = sv.Client( cmd_clientslot );
netadr_t adr( args.Arg( 2 ) );
netadr_t adrClient = pClient->m_NetChannel->GetRemoteAddress();
if ( !adrClient.IsLoopback() && !adr.CompareAdr( adrClient, true ) )
{
ConMsg( "Invalid server IP address.\n" );
return;
}
adr.SetPort( atoi( args.Arg( 4 ) ) );
// NOTE: Trickiness. Need to activate cheats, and that *must* be controlled via
// the server in a method protected by IP checks.
if ( g_nRptServerSlot < 0 )
{
g_nRptServerSlot = cmd_clientslot;
}
char pBuf[256];
Q_snprintf( pBuf, sizeof(pBuf), "rpt_connect %s\n", adr.ToString() );
SV_ExecuteRemoteCommand( pBuf, g_nRptClientSlot );
}
#ifndef SWDS
// NOTE: This executes on the client, and is generated by the server automatically
// when another client tries to connect
CON_COMMAND_F( rpt_connect, "", FCVAR_DONTRECORD | FCVAR_HIDDEN | FCVAR_SERVER_CAN_EXECUTE )
{
if ( CommandLine()->FindParm( "-rpt" ) == 0 )
return;
if ( args.ArgC() != 4 )
return;
const char *pAddress = args.Arg( 1 );
netadr_t adr( pAddress );
adr.SetPort( atoi( args.Arg( 3 ) ) );
// Only accept this from clients inside of valve
if ( !IsValveIPAddress( adr ) )
return;
RPTServer().ConnectToListeningClient( adr, true );
}
// Method to run rcon commands on an rpt-controlled machine
CON_COMMAND_F( rpt, "Issue an rpt command.", FCVAR_DONTRECORD | FCVAR_HIDDEN )
{
char message[1024]; // Command message
char szParam[ 256 ];
message[0] = 0;
for (int i=1 ; i<args.ArgC() ; i++)
{
const char *pParam = args[i];
// put quotes around empty arguments so we can pass things like this: rcon sv_password ""
// otherwise the "" on the end is lost
if ( strchr( pParam, ' ' ) || ( Q_strlen( pParam ) == 0 ) )
{
Q_snprintf( szParam, sizeof( szParam ), "\"%s\"", pParam );
Q_strncat( message, szParam, sizeof( message ), COPY_ALL_CHARACTERS );
}
else
{
Q_strncat( message, pParam, sizeof( message ), COPY_ALL_CHARACTERS );
}
if ( i != ( args.ArgC() - 1 ) )
{
Q_strncat (message, " ", sizeof( message ), COPY_ALL_CHARACTERS);
}
}
RPTClient().SendCmd( message );
}
#endif // SWDS
#endif // ENABLE_RPT
|