• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 12782377031

15 Jan 2025 05:52AM UTC coverage: 57.311% (-0.03%) from 57.34%
12782377031

push

github

web-flow
simplified worldsave commit, do not ignore errors during rename (#744)

15 of 19 new or added lines in 1 file covered. (78.95%)

16 existing lines in 6 files now uncovered.

41074 of 71669 relevant lines covered (57.31%)

372443.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

44.49
/pol-core/pol/network/clientthread.cpp
1
#include "clientthread.h"
2

3
#include <errno.h>
4
#include <exception>
5
#include <iterator>
6
#include <stddef.h>
7
#include <string>
8

9
#include "../../bscript/bobject.h"
10
#include "../../clib/esignal.h"
11
#include "../../clib/fdump.h"
12
#include "../../clib/logfacility.h"
13
#include "../../clib/network/singlepoller.h"
14
#include "../../clib/passert.h"
15
#include "../../clib/spinlock.h"
16
#include "../../clib/stlutil.h"
17
#include "../../plib/systemstate.h"
18
#include "../accounts/account.h"
19
#include "../clib/network/sockets.h"
20
#include "../core.h"
21
#include "../crypt/cryptbase.h"
22
#include "../mobile/charactr.h"
23
#include "../polclock.h"
24
#include "../polsem.h"
25
#include "../schedule.h"
26
#include "../scrdef.h"
27
#include "../scrsched.h"
28
#include "../uoscrobj.h"
29
#include "../uworld.h"
30
#include "cgdata.h"  // This might not be needed if the client has a clear_gd() method
31
#include "client.h"
32
#include "msgfiltr.h"  // Client could also have a method client->is_msg_allowed(), for example. Then this is not needed here.
33
#include "msghandl.h"
34
#include "packethelper.h"
35
#include "packets.h"
36
#include "pktboth.h"
37
#include "pktbothid.h"
38
#include "pktdef.h"
39
#include "pktinid.h"
40
#include "proxyprotocol.h"
41

42

43
#define CLIENT_CHECKPOINT( x ) client->session()->checkpoint = x
44
#define SESSION_CHECKPOINT( x ) session->checkpoint = x
45

46
namespace Pol::Core
47
{
48
// This function below is defined (for now) in pol.cpp. That's ugly.
49
void call_chr_scripts( Mobile::Character* chr, const std::string& root_script_ecl,
50
                       const std::string& pkg_script_ecl, bool offline = false );
51

52

53
void report_weird_packet( Network::ThreadedClient* session,
54
                          const std::string& why );  // Defined below
55

56
void set_polling_timeouts( Clib::SinglePoller& poller, bool single_threaded_login )
16✔
57
{
58
  poller.set_timeout(
30✔
59
      single_threaded_login ? Plib::systemstate.config.loginserver_select_timeout_msecs : 2000 );
14✔
60
}
16✔
61

62
// Taking a reference to SinglePoller is ugly here. But io_step, io_loop and clientpoller will
63
// eventually move into the same class.
64
bool threadedclient_io_step( Network::ThreadedClient* session, Clib::SinglePoller& clientpoller,
124✔
65
                             int& nidle )
66
{
67
  SESSION_CHECKPOINT( 1 );
124✔
68
  if ( !clientpoller.prepare( session->have_queued_data() ) )
124✔
69
  {
70
    POLLOG_INFO( "Client#{}: ERROR - couldn't poll socket={}\n", session->myClient.instance_,
×
71
                 session->csocket );
×
72

73
    if ( session->csocket != INVALID_SOCKET )
×
74
      session->forceDisconnect();
×
75

76
    return false;
×
77
  }
78

79
  int res = 0;
124✔
80
  do
81
  {
82
    SESSION_CHECKPOINT( 2 );
124✔
83
    res = clientpoller.wait_for_events();
124✔
84
    SESSION_CHECKPOINT( 3 );
124✔
85
  } while ( res < 0 && !Clib::exit_signalled && socket_errno == SOCKET_ERRNO( EINTR ) );
124✔
86

87
  if ( res < 0 )
124✔
88
  {
89
    int sckerr = socket_errno;
×
90
    POLLOGLN( "Client#{}: select res={}, sckerr={}", session->myClient.instance_, res, sckerr );
×
91
    return false;
×
92
  }
93
  else if ( res == 0 )
124✔
94
  {
95
    if ( session->myClient.should_check_idle() )
19✔
96
    {
97
      ++nidle;
19✔
98
      if ( nidle == 30 * Plib::systemstate.config.inactivity_warning_timeout )
19✔
99
      {
100
        SESSION_CHECKPOINT( 4 );
×
101
        PolLock lck;  // multithread
×
102
        session->myClient.warn_idle();
×
103
      }
×
104
      else if ( nidle == 30 * Plib::systemstate.config.inactivity_disconnect_timeout )
19✔
105
      {
106
        session->forceDisconnect();
×
107
      }
108
    }
109
  }
110

111
  SESSION_CHECKPOINT( 19 );
124✔
112
  if ( !session->isReallyConnected() )
124✔
113
    return false;
×
114

115
  if ( clientpoller.error() )
124✔
116
  {
UNCOV
117
    session->forceDisconnect();
×
UNCOV
118
    return false;
×
119
  }
120

121
  // region Speedhack
122
  if ( session->has_delayed_packets() )  // not empty then process the first packet
124✔
123
  {
124
    PolLock lck;  // multithread
×
125
    session->process_delayed_packets();
×
126
  }
×
127
  // endregion Speedhack
128

129
  if ( clientpoller.incoming() )
124✔
130
  {
131
    SESSION_CHECKPOINT( 6 );
105✔
132
    if ( process_data( session ) )
105✔
133
    {
134
      SESSION_CHECKPOINT( 17 );
97✔
135
      PolLock lck;
97✔
136

137
      // reset packet timer
138
      session->last_packet_at = polclock();
97✔
139
      if ( !check_inactivity( session ) )
97✔
140
      {
141
        nidle = 0;
80✔
142
        session->last_activity_at = polclock();
80✔
143
      }
144

145
      SESSION_CHECKPOINT( 7 );
97✔
146
      send_pulse();
97✔
147
      if ( TaskScheduler::is_dirty() )
97✔
148
        wake_tasks_thread();
×
149
    }
97✔
150
  }
151

152
  polclock_t polclock_now = polclock();
124✔
153
  if ( ( ( polclock_now - session->last_packet_at ) / POLCLOCKS_PER_SEC ) >= 120 )  // 2 mins
124✔
154
  {
155
    session->forceDisconnect();
×
156
    return false;
×
157
  }
158

159
  if ( session->have_queued_data() && clientpoller.writable() )
124✔
160
  {
161
    PolLock lck;
×
162
    SESSION_CHECKPOINT( 8 );
×
163
    session->send_queued_data();
×
164
  }
×
165
  SESSION_CHECKPOINT( 21 );
124✔
166

167
  return true;
124✔
168
}  // namespace Pol::Core
169

170
void threadedclient_io_loop( Network::ThreadedClient* session, bool login )
16✔
171
{
172
  int nidle = 0;
16✔
173
  session->last_packet_at = polclock();
16✔
174
  session->last_activity_at = polclock();
16✔
175

176
  Clib::SinglePoller clientpoller( session->csocket );
16✔
177
  set_polling_timeouts( clientpoller, login );
16✔
178

179
  while ( !Clib::exit_signalled && session->isReallyConnected() )
126✔
180
  {
181
    if ( !threadedclient_io_step( session, clientpoller, nidle ) || login )
124✔
182
      break;
14✔
183
  }
184
}
16✔
185

186
// Sleeps taking exit_signalled into account
187
void threadedclient_sleep_until( polclock_t when_logoff )
×
188
{
189
  while ( !Clib::exit_signalled )
×
190
  {
191
    if ( polclock() >= when_logoff )
×
192
      break;
×
193
    pol_sleep_ms( 2000 );  // min(2000, when_logoff - polclock()) ?
×
194
  }
195
}
×
196

197
void threadedclient_io_finalize( Network::ThreadedClient* session )
4✔
198
{
199
  int seconds_wait = 0;
4✔
200
  {
201
    SESSION_CHECKPOINT( 9 );
4✔
202
    PolLock lck;
4✔
203
    seconds_wait = session->myClient.on_close();
4✔
204
  }
4✔
205

206
  SESSION_CHECKPOINT( 10 );
4✔
207
  if ( seconds_wait > 0 )
4✔
208
  {
209
    polclock_t when_logoff = session->last_activity_at + seconds_wait * POLCLOCKS_PER_SEC;
×
210
    threadedclient_sleep_until( when_logoff );
×
211
  }
212

213
  SESSION_CHECKPOINT( 15 );
4✔
214
  if ( session->myClient.chr )
4✔
215
  {
216
    PolLock lck;
2✔
217
    session->myClient.on_logoff();
2✔
218
  }
2✔
219
}
4✔
220

221
bool client_io_thread( Network::Client* client, bool login )
16✔
222
{
223
  if ( !login && Plib::systemstate.config.loglevel >= 11 )
16✔
224
  {
225
    POLLOGLN( "Network::Client#{} i/o thread starting", client->instance_ );
×
226
  }
227

228
  CLIENT_CHECKPOINT( 0 );
16✔
229
  try
230
  {
231
    threadedclient_io_loop( client->session(), login );
16✔
232
  }
233
  catch ( std::string& str )
×
234
  {
235
    POLLOG_ERRORLN( "Client#{}: Exception in i/o thread: {}! (checkpoint={})", client->instance_,
×
236
                    str, client->session()->checkpoint );
×
237
  }
×
238
  catch ( const char* msg )
×
239
  {
240
    POLLOG_ERRORLN( "Client#{}: Exception in i/o thread: {}! (checkpoint={})", client->instance_,
×
241
                    msg, client->session()->checkpoint );
×
242
  }
×
243
  catch ( std::exception& ex )
×
244
  {
245
    POLLOG_ERRORLN( "Client#{}: Exception in i/o thread: {}! (checkpoint={})", client->instance_,
×
246
                    ex.what(), client->session()->checkpoint );
×
247
  }
×
248
  CLIENT_CHECKPOINT( 20 );
16✔
249

250
  if ( login && client->isConnected() )
16✔
251
    return true;
12✔
252

253
  POLLOGLN( "Client#{} ({}): disconnected (account {})", client->instance_,
4✔
254
            client->ipaddrAsString(),
4✔
255
            ( ( client->acct != nullptr ) ? client->acct->name() : "unknown" ) );
4✔
256

257
  try
258
  {
259
    threadedclient_io_finalize( client->session() );
4✔
260
  }
261
  catch ( std::exception& ex )
×
262
  {
263
    POLLOGLN( "Client#{}: Exception in i/o thread: {}! (checkpoint={}, what={})", client->instance_,
×
264
              client->session()->checkpoint, ex.what() );
×
265
  }
×
266

267
  // queue delete of client ptr see method doc for reason
268
  Core::networkManager.clientTransmit->QueueDelete( client );
4✔
269
  return false;
4✔
270
}
271

272
bool valid_message_length( Network::ThreadedClient* session, unsigned int length )
13✔
273
{
274
  if ( length > sizeof session->buffer )
13✔
275
  {
276
    handle_humongous_packet( session, session->message_length );
×
277
    return false;
×
278
  }
279
  if ( length < 3 )
13✔
280
  {
281
    report_weird_packet( session, "Too-short message" );
×
282
    return false;
×
283
  }
284
  return true;
13✔
285
}
286

287
// bool - return true when a message was processed.
288
bool process_data( Network::ThreadedClient* session )
105✔
289
{
290
  // NOTE: This is coded such that for normal messages, which are completely available,
291
  // this function will get the type, then the length, then the data, without having
292
  // to wait for a second or third call.
293
  // Also, the abnormal state, RECV_STATE_CRYPTSEED_WAIT, is handled at the end, so in
294
  // normal processing its code doesn't see the code path.
295
  passert( session->bufcheck1_AA == 0xAA );
105✔
296
  passert( session->bufcheck2_55 == 0x55 );
105✔
297
  if ( session->recv_state == Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT )
105✔
298
  {
299
    session->bytes_received = 0;
101✔
300
    session->recv_remaining( 1 );
101✔
301
    SESSION_CHECKPOINT( 22 );
101✔
302
    if ( session->bytes_received < 1 )  // this really should never happen.
101✔
303
    {
304
      session->forceDisconnect();
4✔
305
      return false;
4✔
306
    }
307

308
    unsigned char msgtype = session->buffer[0];
97✔
309
    session->last_msgtype = msgtype;  // CNXBUG
97✔
310
    if ( Plib::systemstate.config.verbose )
97✔
311
      INFO_PRINTLN( "Incoming msg type: {:#x}", (int)msgtype );
×
312

313
    if ( !Network::PacketRegistry::is_defined( msgtype ) )
97✔
314
    {
315
      handle_undefined_packet( session );
×
316
      if ( !session->myClient.chr && Plib::systemstate.config.loginserver_disconnect_unknown_pkts )
×
317
        session->forceDisconnect();
×
318
      return false;  // remain in RECV_STATE_MSGTYPE_WAIT
×
319
    }
320
    // during login if it should disconnect no need to receive the data before killing the
321
    // connection
322
    if ( !session->myClient.chr && Plib::systemstate.config.loginserver_disconnect_unknown_pkts &&
97✔
323
         !session->msgtype_filter->msgtype_allowed[msgtype] )
8✔
324
    {
325
      POLLOG_ERRORLN( "Client#{} ({}, Acct {}) sent non-allowed message type {:#x}.",
×
326
                      session->myClient.instance_, session->ipaddrAsString(),
×
327
                      ( session->myClient.acct ? session->myClient.acct->name() : "unknown" ),
×
328
                      (int)msgtype );
×
329
      session->forceDisconnect();
×
330
      return false;
×
331
    }
332

333
    Network::MSG_HANDLER packetHandler =
334
        Network::PacketRegistry::find_handler( msgtype, &session->myClient );
97✔
335
    if ( packetHandler.msglen == MSGLEN_2BYTELEN_DATA )
97✔
336
    {
337
      session->recv_state = Network::ThreadedClient::RECV_STATE_MSGLEN_WAIT;
13✔
338
    }
339
    else
340
    {
341
      passert( packetHandler.msglen > 0 );
84✔
342

343
      session->recv_state = Network::ThreadedClient::RECV_STATE_MSGDATA_WAIT;
84✔
344
      session->message_length = packetHandler.msglen;
84✔
345
    }
346

347
  } /* endif of RECV_STATE_MSGTYPE_WAIT */
348

349
  if ( session->recv_state == Network::ThreadedClient::RECV_STATE_MSGLEN_WAIT )
101✔
350
  {
351
    session->recv_remaining( 3 );
13✔
352
    SESSION_CHECKPOINT( 23 );
13✔
353
    if ( session->bytes_received == 3 )  // the length bytes were available.
13✔
354
    {
355
      // MSG is [MSGTYPE] [LENHI] [LENLO] [DATA ... ]
356
      session->message_length = ( session->buffer[1] << 8 ) + session->buffer[2];
13✔
357

358
      if ( !valid_message_length( session, session->message_length ) )
13✔
359
      {
360
        // If the reported length is too short (less than 3 bytes) or
361
        // too big (larger than the client buffer), something very odd
362
        // happened.
363
        session->forceDisconnect();
×
364
        return false;
×
365
      }
366
      session->recv_state = Network::ThreadedClient::RECV_STATE_MSGDATA_WAIT;
13✔
367
    }
368
    // else keep waiting.
369
  } /* endif of RECV_STATE_MSGLEN_WAIT */
370

371
  if ( session->recv_state == Network::ThreadedClient::RECV_STATE_MSGDATA_WAIT )
101✔
372
  {
373
    SESSION_CHECKPOINT( 24 );
97✔
374
    session->recv_remaining( session->message_length );
97✔
375
    SESSION_CHECKPOINT( 25 );
97✔
376
    if ( session->bytes_received == session->message_length )  // we have the whole message
97✔
377
    {
378
      unsigned char msgtype = session->buffer[0];
97✔
379
      networkManager.iostats.received[msgtype].count++;
97✔
380
      networkManager.iostats.received[msgtype].bytes += session->message_length;
97✔
381
      {
382
        // Consider if the client should do the logging via client->log_incoming(...) instead. Also,
383
        // can we avoid the SpinLock here?
384
        Clib::SpinLockGuard guard( session->_fpLog_lock );
97✔
385
        if ( !session->fpLog.empty() )
97✔
386
        {
387
          std::string tmp = fmt::format( "Client -> Server: {:#x}, {} bytes\n", msgtype,
388
                                         session->message_length );
×
389
          Clib::fdump( std::back_inserter( tmp ), &session->buffer, session->message_length );
×
390
          FLEXLOGLN( session->fpLog, tmp );
×
391
        }
×
392
      }
97✔
393

394
      if ( Plib::systemstate.config.verbose )
97✔
395
        INFO_PRINTLN( "Message Received: Type {:#x}, Length {} bytes", (int)msgtype,
×
396
                      session->message_length );
×
397

398
      if ( session->msgtype_filter->msgtype_allowed[msgtype] )
97✔
399
      {
400
        PolLock lck;  // multithread
97✔
401
        // it can happen that a client gets disconnected while waiting for the lock.
402
        if ( !session->isConnected() )
97✔
403
          return false;
×
404

405
        // region Speedhack
406
        if ( ( settingsManager.ssopt.speedhack_prevention ) && ( msgtype == PKTIN_02_ID ) )
97✔
407
        {
408
          if ( !session->myClient.SpeedHackPrevention() )
×
409
          {
410
            // client->SpeedHackPrevention() added packet to queue
411
            session->recv_state = Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT;
×
412
            SESSION_CHECKPOINT( 28 );
×
413
            return true;
×
414
          }
415
        }
416
        // endregion Speedhack
417

418
        session->myClient.handle_msg( session->buffer, session->bytes_received );
97✔
419
        session->recv_state = Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT;
97✔
420
        SESSION_CHECKPOINT( 28 );
97✔
421
        return true;
97✔
422
      }
97✔
423
      else
424
      {
425
        // Such combinations of instance and acct happen quite often. Maybe this should become
426
        // Client->full_id() or something.
427
        POLLOG_ERRORLN( "Client#{} ({}, Acct {}) sent non-allowed message type {:#x}.",
×
428
                        session->myClient.instance_, session->ipaddrAsString(),
×
429
                        ( session->myClient.acct ? session->myClient.acct->name() : "unknown" ),
×
430
                        (int)msgtype );
×
431
      }
432
      session->recv_state = Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT;
×
433
      SESSION_CHECKPOINT( 28 );
×
434
      return false;
×
435
    }
436
    // else keep waiting
437
  } /* endif RECV_STATE_MSGDATA_WAIT */
438
  else if ( session->recv_state == Network::ThreadedClient::RECV_STATE_CRYPTSEED_WAIT )
4✔
439
  {  // The abnormal case.
440
    // The first four bytes after connection are the
441
    // crypto seed
442
    session->recv_remaining_nocrypt( 4 );
4✔
443

444
    if ( session->bytes_received == 4 )
4✔
445
    {
446
      /* The first four bytes transmitted are the encryption seed */
447
      unsigned char cstype = session->buffer[0];
4✔
448

449
      if ( ( session->buffer[0] == 0xff ) && ( session->buffer[1] == 0xff ) &&
4✔
450
           ( session->buffer[2] == 0xff ) && ( session->buffer[3] == 0xff ) )
×
451
      {
452
        if ( Plib::systemstate.config.verbose )
×
453
        {
454
          INFO_PRINTLN( "UOKR Seed Message Received: Type {:#x}", (int)cstype );
×
455
        }
456
        session->myClient.send_KR_encryption_response();
×
457
        session->myClient.setClientType( Network::CLIENTTYPE_UOKR );  // UO:KR logging in
×
458
        session->recv_state = Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT;
×
459
      }
460
      else if ( session->buffer[0] == PKTIN_EF_ID )
4✔
461
      {
462
        // new seed since 6.0.5.0 (0xef should never appear in normal ipseed)
463
        if ( Plib::systemstate.config.verbose )
2✔
464
        {
465
          INFO_PRINTLN( "6.0.5.0+ Crypt Seed Message Received: Type {:#x}", (int)cstype );
×
466
        }
467
        session->recv_state = Network::ThreadedClient::RECV_STATE_CLIENTVERSION_WAIT;
2✔
468
      }
469
      else
470
      {
471
        session->cryptengine->Init( session->buffer, Crypt::CCryptBase::typeAuto );
2✔
472
        session->recv_state = Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT;
2✔
473
      }
474
    }
475
    // Else keep waiting for IP address.
476
  }
477
  else if ( session->recv_state == Network::ThreadedClient::RECV_STATE_PROXYPROTOCOLHEADER_WAIT )
×
478
  {
479
    session->recv_remaining_nocrypt( sizeof( pp_header_v2 ) );
×
480

481
    if ( session->bytes_received == sizeof( pp_header_v2 ) )
×
482
    {
483
      pp_header_v2* pp_header = reinterpret_cast<pp_header_v2*>( &session->buffer );
×
484

485
      if ( !pp_header->is_valid() )
×
486
      {
487
        POLLOGLN( "Client#{} ({}): disconnected due to invalid proxy header",
×
488
                  session->myClient.instance_, session->ipaddrAsString() );
×
489

490
        // invalid header
491
        session->forceDisconnect();
×
492
        return false;
×
493
      }
494

495
      if ( pp_header->command() == PP_CMD_LOCAL && pp_header->payload_size() == 0 )
×
496
      {
497
        // for local command with zero payload there is nothing else to read => continue with the
498
        // stream
499
        session->recv_state = Network::ThreadedClient::RECV_STATE_CRYPTSEED_WAIT;
×
500
        session->bytes_received = 0;
×
501
      }
502
      else
503
      {
504
        // other cases need to read payload first
505
        session->recv_state = Network::ThreadedClient::RECV_STATE_PROXYPROTOCOLPAYLOAD_WAIT;
×
506
      }
507
    }
508
    // Else keep waiting
509
  }
510

511
  if ( session->recv_state == Network::ThreadedClient::RECV_STATE_PROXYPROTOCOLPAYLOAD_WAIT )
4✔
512
  {
513
    pp_header_v2* pp_header = reinterpret_cast<pp_header_v2*>( &session->buffer );
×
514
    session->recv_remaining_nocrypt( sizeof( pp_header_v2 ) + pp_header->payload_size() );
×
515

516
    if ( session->bytes_received == sizeof( pp_header_v2 ) + pp_header->payload_size() )
×
517
    {
518
      pp_payload_v2* pp_payload =
×
519
          reinterpret_cast<pp_payload_v2*>( &session->buffer[sizeof( pp_header_v2 )] );
520

521
      if ( pp_header->command() == PP_CMD_LOCAL )
×
522
      {
523
        // local commands shouldn't have payload, but in case they do it should be skipped
524
        session->recv_state = Network::ThreadedClient::RECV_STATE_CRYPTSEED_WAIT;
×
525
        return true;
×
526
      }
527

528
      if ( pp_header->command() == PP_CMD_PROXY && pp_header->protocol() == PP_TP_STREAM )
×
529
      {
530
        bool was_proxied = false;
×
531

532
        if ( pp_header->address_family() == PP_AF_INET &&
×
533
             pp_header->payload_size() >= sizeof( pp_payload->ipv4_addr ) )
×
534
        {
535
          memcpy( &session->ipaddr_proxy, &session->ipaddr, sizeof( session->ipaddr_proxy ) );
×
536
          memset( &session->ipaddr, 0, sizeof( session->ipaddr ) );
×
537

538
          auto ipaddr_in = reinterpret_cast<sockaddr_in*>( &session->ipaddr );
×
539
          ipaddr_in->sin_family = AF_INET;
×
540
          ipaddr_in->sin_port = pp_payload->ipv4_addr.src_port;
×
541
          memcpy( &ipaddr_in->sin_addr, &pp_payload->ipv4_addr.src_addr,
×
542
                  sizeof( ipaddr_in->sin_addr ) );
543

544
          was_proxied = true;
×
545
        }
546

547
        /* IPv6 isn't supported
548
        if ( pp_header->address_family() == PP_AF_INET6 &&
549
             pp_header->payload_size() >= sizeof( pp_payload->ipv6_addr ) )
550
        {
551
          memcpy( &session->ipaddr_proxy, &session->ipaddr, sizeof( session->ipaddr_proxy ) );
552
          memset( &session->ipaddr, 0, sizeof( session->ipaddr ) );
553

554
          auto ipaddr_in6 = reinterpret_cast<sockaddr_in6*>( &session->ipaddr );
555
          ipaddr_in6->sin6_family = AF_INET6;
556
          ipaddr_in6->sin6_port = pp_payload->ipv6_addr.src_port;
557
          memcpy( &ipaddr_in6->sin6_addr, &pp_payload->ipv6_addr.src_addr,
558
                  sizeof(ipaddr_in6->sin6_addr) );
559

560
          was_proxied = true;
561
        }
562
        */
563

564
        if ( was_proxied )
×
565
        {
566
          POLLOGLN( "Client#{} ({}): connected through proxy {}", session->myClient.instance_,
×
567
                    session->ipaddrAsString(), session->ipaddrProxyAsString() );
×
568

569
          session->recv_state = Network::ThreadedClient::RECV_STATE_CRYPTSEED_WAIT;
×
570
          session->bytes_received = 0;
×
571
          return true;
×
572
        }
573
      }
574

575
      // unsupported combination
576
      POLLOGLN( "Client#{} ({}): disconnected due to unsupported proxy payload",
×
577
                session->myClient.instance_, session->ipaddrAsString() );
×
578

579
      session->forceDisconnect();
×
580
      return false;
×
581
    }
582
    // Else keep waiting
583
  }
584

585
  if ( session->recv_state == Network::ThreadedClient::RECV_STATE_CLIENTVERSION_WAIT )
4✔
586
  {
587
    // receive and send to handler to get directly the version
588
    session->recv_remaining_nocrypt( 21 );
2✔
589
    if ( session->bytes_received == 21 )
2✔
590
    {
591
      session->recv_state = Network::ThreadedClient::RECV_STATE_MSGTYPE_WAIT;
2✔
592
      unsigned char tempseed[4];
593
      tempseed[0] = session->buffer[1];
2✔
594
      tempseed[1] = session->buffer[2];
2✔
595
      tempseed[2] = session->buffer[3];
2✔
596
      tempseed[3] = session->buffer[4];
2✔
597
      session->cryptengine->Init( tempseed, Crypt::CCryptBase::typeLogin );
2✔
598
      Network::PacketRegistry::handle_msg( PKTIN_EF_ID, &session->myClient, session->buffer );
2✔
599
    }
600
  }
601

602
  return false;
4✔
603
}
604

605
// TODO: We may want to take a buffer directly here instead of a ThreadedClient
606
bool check_inactivity( Network::ThreadedClient* session )
97✔
607
{
608
  switch ( session->buffer[0] )
97✔
609
  {
610
  case PKTBI_73_ID:
17✔
611
  // Fallthrough
612
  case PKTIN_09_ID:
613
  // Fallthrough
614
  case PKTBI_D6_IN_ID:
615
    return true;
17✔
616
  case PKTBI_BF_ID:
7✔
617
    if ( ( session->buffer[3] == 0 ) && ( session->buffer[4] == PKTBI_BF::TYPE_SESPAM ) )
7✔
618
      return true;
×
619
    break;
7✔
620
  default:
73✔
621
    return false;
73✔
622
  }
623

624
  return false;
7✔
625
}
626

627
// Something to consider: this could become Client::report_weird_packet(char* buffer, int
628
// bytes_received, const std::string& why)
629
void report_weird_packet( Network::ThreadedClient* session, const std::string& why )
×
630
{
631
  std::string tmp = fmt::format(
632
      "Client#{}: {} type {:#x}, {} bytes (IP: {}, Account: {})\n", session->myClient.instance_,
×
633
      why, (int)session->buffer[0], session->bytes_received, session->ipaddrAsString(),
×
634
      ( session->myClient.acct != nullptr ) ? session->myClient.acct->name() : "None" );
×
635

636
  if ( session->bytes_received <= 64 )
×
637
  {
638
    Clib::fdump( std::back_inserter( tmp ), session->buffer, session->bytes_received );
×
639
    POLLOG_INFO( tmp );
×
640
  }
641
  else
642
  {
643
    INFO_PRINT( tmp );
×
644
    Clib::fdump( std::back_inserter( tmp ), session->buffer, session->bytes_received );
×
645
    POLLOGLN( tmp );
×
646
  }
647
}
×
648

649
// Called when a packet size is registered but the
650
// packet has no handler in the core
651
void handle_unknown_packet( Network::ThreadedClient* session )
×
652
{
653
  if ( Plib::systemstate.config.display_unknown_packets )
×
654
    report_weird_packet( session, "Unknown packet" );
×
655
}
×
656

657
// Called when POL receives an undefined packet.
658
// Those have no registered size, so we must guess.
659
void handle_undefined_packet( Network::ThreadedClient* session )
×
660
{
661
  int msgtype = (int)session->buffer[0];
×
662
  INFO_PRINTLN( "Undefined message type {:#x}", msgtype );
×
663

664
  // Tries to read as much of it out as possible
665
  session->recv_remaining( sizeof session->buffer / 2 );
×
666

667
  report_weird_packet( session, "Unexpected message" );
×
668
}
×
669

670
// Handles variable-sized packets whose declared size is much larger than
671
// the receive buffer. This packet is most likely a client error, because
672
// the buffer should be big enough to handle anything sent by the known
673
// clients.
674
void handle_humongous_packet( Network::ThreadedClient* session, unsigned int reported_size )
×
675
{
676
  // Tries to read as much of it out as possible
677
  // (the client will be disconnected, but this may
678
  // be useful for debugging)
679
  session->recv_remaining( sizeof session->buffer / 2 );
×
680

681
  report_weird_packet( session, fmt::format( "Humongous packet (length {})", reported_size ) );
×
682
}
×
683
}  // namespace Pol::Core
684

685
namespace Pol::Network
686
{
687
// on_close determines how long to wait until on_logoff is called. An alternative would be to call
688
// test_logoff directly in the threadedclient_io_finalize.
689
int Client::on_close()
4✔
690
{
691
  unregister();
4✔
692
  INFO_PRINTLN( "Client disconnected from {} ({}/{} connections)", ipaddrAsString(),
4✔
693
                Core::networkManager.clients.size(),
4✔
694
                Core::networkManager.getNumberOfLoginClients() );
4✔
695

696
  Core::CoreSetSysTrayToolTip(
4✔
697
      Clib::tostring( Core::networkManager.clients.size() ) + " clients connected",
8✔
698
      Core::ToolTipPrioritySystem );
699

700
  if ( chr )
4✔
701
  {
702
    chr->disconnect_cleanup();
2✔
703
    gd->clear();
2✔
704
    chr->connected( false );
2✔
705
  }
706

707
  return test_logoff();
4✔
708
}
709

710
int Client::test_logoff()
4✔
711
{
712
  if ( !chr )
4✔
713
    return 0;
2✔
714

715
  int seconds_wait = 0;
2✔
716
  Core::ScriptDef sd;
2✔
717
  sd.quickconfig( "scripts/misc/logofftest.ecl" );
2✔
718
  if ( sd.exists() )
2✔
719
  {
720
    Bscript::BObject bobj( run_script_to_completion( sd, new Module::ECharacterRefObjImp( chr ) ) );
×
721
    if ( bobj.isa( Bscript::BObjectImp::OTLong ) )
×
722
    {
723
      const Bscript::BLong* blong = bobj.impptr<Bscript::BLong>();
×
724
      seconds_wait = blong->value();
×
725
    }
726
  }
×
727
  return seconds_wait;
2✔
728
}
2✔
729

730
void Client::on_logoff()
2✔
731
{
732
  if ( chr )
2✔
733
  {
734
    call_chr_scripts( chr, "scripts/misc/logoff.ecl", "logoff.ecl" );
2✔
735
    if ( chr->realm() )
2✔
736
    {
737
      chr->realm()->notify_left( *chr );
2✔
738
    }
739
  }
740
}
2✔
741

742
void Client::warn_idle()
×
743
{
744
  Network::PktHelper::PacketOut<Network::PktOut_53> msg;
×
745
  msg->Write<u8>( PKTOUT_53_WARN_CHARACTER_IDLE );
×
746
  msg.Send( this );
×
747

748
  if ( pause_count )
×
749
    restart();
×
750
}
×
751

752
bool Client::should_check_idle()
19✔
753
{
754
  return ( !chr || chr->cmdlevel() < Plib::systemstate.config.min_cmdlvl_ignore_inactivity ) &&
38✔
755
         Plib::systemstate.config.inactivity_warning_timeout &&
38✔
756
         Plib::systemstate.config.inactivity_disconnect_timeout && !disable_inactivity_timeout();
57✔
757
}
758

759
void Client::handle_msg( unsigned char* pktbuffer, int pktlen )
97✔
760
{
761
  const unsigned char msgtype = pktbuffer[0];
97✔
762
  try
763
  {
764
    INFO_PRINTLN_TRACE( 10 )( "Client#{}: message {:#x}", instance_, msgtype );
97✔
765

766
    // TODO: use PacketRegistry::handle_msg(...) ?
767
    MSG_HANDLER packetHandler = Network::PacketRegistry::find_handler( msgtype, this );
97✔
768
    passert( packetHandler.msglen != 0 );
97✔
769
    packetHandler.func( this, pktbuffer );
97✔
770
    Core::restart_all_clients();
97✔
771
  }
772
  catch ( std::exception& ex )
×
773
  {
774
    POLLOG_ERRORLN( "Client#{}: Exception in message handler {:#x}: {}", instance_, (int)msgtype,
×
775
                    ex.what() );
×
776

777
    std::string tmp;
×
778
    Clib::fdump( std::back_inserter( tmp ), pktbuffer, pktlen );
×
779
    POLLOGLN( tmp );
×
780

781
    Core::restart_all_clients();
×
782
    throw;
×
783
  }
×
784
}
97✔
785

786
void Client::send_KR_encryption_response()
×
787
{
788
  Network::PktHelper::PacketOut<Network::PktOut_E3> msg;
×
789
  msg->WriteFlipped<u16>( 77u );
×
790
  msg->WriteFlipped<u32>( 0x03u );
×
791
  msg->Write<u8>( 0x02u );
×
792
  msg->Write<u8>( 0x01u );
×
793
  msg->Write<u8>( 0x03u );
×
794
  msg->WriteFlipped<u32>( 0x13u );
×
795
  msg->Write<u8>( 0x02u );
×
796
  msg->Write<u8>( 0x11u );
×
797
  msg->Write<u8>( 0x00u );
×
798
  msg->Write<u8>( 0xfcu );
×
799
  msg->Write<u8>( 0x2fu );
×
800
  msg->Write<u8>( 0xe3u );
×
801
  msg->Write<u8>( 0x81u );
×
802
  msg->Write<u8>( 0x93u );
×
803
  msg->Write<u8>( 0xcbu );
×
804
  msg->Write<u8>( 0xafu );
×
805
  msg->Write<u8>( 0x98u );
×
806
  msg->Write<u8>( 0xddu );
×
807
  msg->Write<u8>( 0x83u );
×
808
  msg->Write<u8>( 0x13u );
×
809
  msg->Write<u8>( 0xd2u );
×
810
  msg->Write<u8>( 0x9eu );
×
811
  msg->Write<u8>( 0xeau );
×
812
  msg->Write<u8>( 0xe4u );
×
813
  msg->Write<u8>( 0x13u );
×
814
  msg->WriteFlipped<u32>( 0x10u );
×
815
  msg->Write<u8>( 0x78u );
×
816
  msg->Write<u8>( 0x13u );
×
817
  msg->Write<u8>( 0xb7u );
×
818
  msg->Write<u8>( 0x7bu );
×
819
  msg->Write<u8>( 0xceu );
×
820
  msg->Write<u8>( 0xa8u );
×
821
  msg->Write<u8>( 0xd7u );
×
822
  msg->Write<u8>( 0xbcu );
×
823
  msg->Write<u8>( 0x52u );
×
824
  msg->Write<u8>( 0xdeu );
×
825
  msg->Write<u8>( 0x38u );
×
826
  msg->Write<u8>( 0x30u );
×
827
  msg->Write<u8>( 0xeau );
×
828
  msg->Write<u8>( 0xe9u );
×
829
  msg->Write<u8>( 0x1eu );
×
830
  msg->Write<u8>( 0xa3u );
×
831
  msg->WriteFlipped<u32>( 0x20u );
×
832
  msg->WriteFlipped<u32>( 0x10u );
×
833
  msg->Write<u8>( 0x5au );
×
834
  msg->Write<u8>( 0xceu );
×
835
  msg->Write<u8>( 0x3eu );
×
836
  msg->Write<u8>( 0xe3u );
×
837
  msg->Write<u8>( 0x97u );
×
838
  msg->Write<u8>( 0x92u );
×
839
  msg->Write<u8>( 0xe4u );
×
840
  msg->Write<u8>( 0x8au );
×
841
  msg->Write<u8>( 0xf1u );
×
842
  msg->Write<u8>( 0x9au );
×
843
  msg->Write<u8>( 0xd3u );
×
844
  msg->Write<u8>( 0x04u );
×
845
  msg->Write<u8>( 0x41u );
×
846
  msg->Write<u8>( 0x03u );
×
847
  msg->Write<u8>( 0xcbu );
×
848
  msg->Write<u8>( 0x53u );
×
849
  msg.Send( this );
×
850
}
×
851

852

853
//
854

855

856
// ThreadedClient stuff
857
void ThreadedClient::process_delayed_packets()
×
858
{
859
  PacketThrottler pkt = myClient.movementqueue.front();
×
860

861
  if ( myClient.SpeedHackPrevention( false ) )
×
862
  {
863
    if ( isReallyConnected() )
×
864
    {
865
      myClient.handle_msg( pkt.pktbuffer, sizeof pkt.pktbuffer );
×
866
    }
867
    myClient.movementqueue.pop();
×
868
  }
869
}
×
870

871
}  // namespace Pol::Network
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc