301 lines
8.2 KiB
C++
301 lines
8.2 KiB
C++
/*
|
|
Copyright (c) 2009-2010 Christopher A. Taylor. All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
* Neither the name of LibCat nor the names of its contributors may be used
|
|
to endorse or promote products derived from this software without
|
|
specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifndef CAT_SPHYNX_SERVER_HPP
|
|
#define CAT_SPHYNX_SERVER_HPP
|
|
|
|
#include <cat/net/SphynxTransport.hpp>
|
|
#include <cat/threads/RWLock.hpp>
|
|
#include <cat/threads/Thread.hpp>
|
|
#include <cat/threads/WaitableFlag.hpp>
|
|
#include <cat/net/SphynxCollexion.hpp>
|
|
#include <cat/crypt/cookie/CookieJar.hpp>
|
|
#include <cat/crypt/tunnel/KeyAgreementResponder.hpp>
|
|
|
|
namespace cat {
|
|
|
|
|
|
namespace sphynx {
|
|
|
|
|
|
/*
|
|
Designed for server hardware with many processors.
|
|
|
|
In order to handle many users, the Sphynx server opens up a single UDP port
|
|
to accept new connections, and several other UDP data ports for data.
|
|
|
|
For retransmissions and detecting link loss due to timeout, the server runs
|
|
several additional threads that wake up periodically to perform timed tasks.
|
|
|
|
Server uses thread pool to receive packets on connect and worker ports,
|
|
meaning that packets are processed by any free CPU as soon as they arrive.
|
|
|
|
Sphynx Server
|
|
UDP Hello Port [1]
|
|
+ In case this thread spins constantly, only use one CPU for new
|
|
connections since in-game experience is more important than login
|
|
+ Assigns users to a data port after handshake completes
|
|
|
|
UDP Data Ports [4 * (CPU Count)]
|
|
+ Spread users evenly across several ports since
|
|
only one packet can be processed from a single port at a time
|
|
+ Any free CPU will process incoming packets as fast as possible
|
|
|
|
ServerTimer threads [(CPU Count) / 2]
|
|
+ In case these threads spin constantly, they only consume
|
|
half of the CPU resources available
|
|
+ Wake up every X milliseconds according to Transport::TICK_RATE
|
|
+ Detect link loss due to silence timeout
|
|
+ Update transport layer
|
|
+ Retransmit lost messages
|
|
+ Re-evaluate bandwidth and transmit queued messages
|
|
*/
|
|
|
|
|
|
//// sphynx::Connexion
|
|
|
|
// Derive from sphynx::Connexion and sphynx::Server to define server behavior
|
|
class Connexion : public Transport, public ThreadRefObject
|
|
{
|
|
friend class Server;
|
|
friend class Map;
|
|
friend class ServerWorker;
|
|
friend class ServerTimer;
|
|
|
|
public:
|
|
Connexion();
|
|
virtual ~Connexion() {}
|
|
|
|
private:
|
|
volatile u32 _destroyed;
|
|
|
|
u32 _key; // Map hash table index, unique for each active connection
|
|
Connexion *_next_delete;
|
|
ServerWorker *_server_worker;
|
|
|
|
u8 _first_challenge[64]; // First challenge seen from this client address
|
|
u8 _cached_answer[128]; // Cached answer to this first challenge, to avoid eating server CPU time
|
|
|
|
private:
|
|
// Return false to destroy this object
|
|
bool Tick(ThreadPoolLocalStorage *tls, u32 now);
|
|
|
|
void OnRawData(ThreadPoolLocalStorage *tls, u8 *data, u32 bytes);
|
|
|
|
virtual bool PostPacket(u8 *buffer, u32 buf_bytes, u32 msg_bytes, u32 skip_bytes);
|
|
|
|
public:
|
|
CAT_INLINE bool IsValid() { return _destroyed == 0; }
|
|
CAT_INLINE u32 GetKey() { return _key; }
|
|
|
|
void Destroy();
|
|
|
|
protected:
|
|
NetAddr _client_addr;
|
|
|
|
// Last time a packet was received from this user -- for disconnect timeouts
|
|
u32 _last_recv_tsc;
|
|
|
|
bool _seen_encrypted;
|
|
AuthenticatedEncryption _auth_enc;
|
|
|
|
protected:
|
|
virtual void OnConnect(ThreadPoolLocalStorage *tls) = 0;
|
|
virtual void OnDestroy() = 0;
|
|
virtual void OnTick(ThreadPoolLocalStorage *tls, u32 now) = 0;
|
|
};
|
|
|
|
|
|
//// sphynx::Map
|
|
|
|
class Map
|
|
{
|
|
protected:
|
|
CAT_INLINE u32 hash_addr(const NetAddr &addr, u32 salt);
|
|
|
|
public:
|
|
struct Slot
|
|
{
|
|
Connexion *connection;
|
|
bool collision;
|
|
Slot *next;
|
|
};
|
|
|
|
protected:
|
|
u32 _hash_salt;
|
|
CAT_ALIGNED(16) Slot _table[HASH_TABLE_SIZE];
|
|
RWLock _table_lock;
|
|
|
|
public:
|
|
Map();
|
|
virtual ~Map();
|
|
|
|
// Lookup client by address
|
|
Connexion *Lookup(const NetAddr &addr);
|
|
|
|
// Lookup client by key
|
|
Connexion *Lookup(u32 key);
|
|
|
|
// May return false if network address in Connexion is already in the map.
|
|
// This averts a potential race condition but should never happen during
|
|
// normal operation, so the Connexion allocation by caller won't be wasted.
|
|
bool Insert(Connexion *conn);
|
|
|
|
// Destroy a list described by the 'next' member of Slot
|
|
void DestroyList(Map::Slot *kill_list);
|
|
|
|
void Tick(ThreadPoolLocalStorage *tls);
|
|
};
|
|
|
|
|
|
//// sphynx::ServerWorker
|
|
|
|
class ServerWorker : public UDPEndpoint
|
|
{
|
|
friend class Map;
|
|
|
|
protected:
|
|
Map *_conn_map;
|
|
ServerTimer *_server_timer;
|
|
|
|
protected:
|
|
volatile u32 _session_count;
|
|
|
|
public:
|
|
ServerWorker(Map *conn_map, ServerTimer *server_timer);
|
|
virtual ~ServerWorker();
|
|
|
|
void IncrementPopulation();
|
|
void DecrementPopulation();
|
|
u32 GetPopulation() { return _session_count; }
|
|
|
|
protected:
|
|
void OnRead(ThreadPoolLocalStorage *tls, const NetAddr &src, u8 *data, u32 bytes);
|
|
void OnWrite(u32 bytes) {}
|
|
void OnClose();
|
|
};
|
|
|
|
|
|
//// sphynx::ServerTimer
|
|
|
|
class ServerTimer : Thread
|
|
{
|
|
protected:
|
|
Map *_conn_map;
|
|
|
|
protected:
|
|
ServerWorker **_workers;
|
|
int _worker_count;
|
|
|
|
protected:
|
|
Map::Slot *_insert_head;
|
|
Mutex _insert_lock;
|
|
|
|
protected:
|
|
Map::Slot *_active_head;
|
|
|
|
public:
|
|
ServerTimer(Map *conn_map, ServerWorker **workers, int worker_count);
|
|
virtual ~ServerTimer();
|
|
|
|
CAT_INLINE bool Valid() { return _worker_count > 0; }
|
|
|
|
public:
|
|
void InsertSlot(Map::Slot *slot);
|
|
|
|
protected:
|
|
CAT_INLINE void Tick(ThreadPoolLocalStorage *tls);
|
|
bool ThreadFunction(void *param);
|
|
|
|
protected:
|
|
static const int TIMER_THREAD_KILL_TIMEOUT = 10000;
|
|
|
|
WaitableFlag _kill_flag;
|
|
};
|
|
|
|
|
|
//// sphynx::Server
|
|
|
|
class Server : public UDPEndpoint
|
|
{
|
|
public:
|
|
Server();
|
|
virtual ~Server();
|
|
|
|
bool StartServer(ThreadPoolLocalStorage *tls, Port port, u8 *public_key, int public_bytes, u8 *private_key, int private_bytes, const char *session_key);
|
|
|
|
u32 GetTotalPopulation();
|
|
|
|
static bool GenerateKeyPair(ThreadPoolLocalStorage *tls, const char *public_key_file,
|
|
const char *private_key_file, u8 *public_key,
|
|
int public_bytes, u8 *private_key, int private_bytes);
|
|
|
|
private:
|
|
static const int SESSION_KEY_BYTES = 32;
|
|
char _session_key[SESSION_KEY_BYTES];
|
|
|
|
Port _server_port;
|
|
Map _conn_map;
|
|
|
|
CookieJar _cookie_jar;
|
|
KeyAgreementResponder _key_agreement_responder;
|
|
u8 _public_key[PUBLIC_KEY_BYTES];
|
|
|
|
static const int WORKER_LIMIT = 32; // Maximum number of workers
|
|
ServerWorker **_workers;
|
|
int _worker_count;
|
|
|
|
ServerTimer **_timers;
|
|
int _timer_count;
|
|
|
|
private:
|
|
ServerWorker *FindLeastPopulatedPort();
|
|
|
|
void OnRead(ThreadPoolLocalStorage *tls, const NetAddr &src, u8 *data, u32 bytes);
|
|
void OnWrite(u32 bytes);
|
|
void OnClose();
|
|
|
|
protected:
|
|
// Must return a new instance of your Connexion derivation
|
|
virtual Connexion *NewConnexion() = 0;
|
|
|
|
// IP address filter: Return true to allow the connection to be made
|
|
virtual bool AcceptNewConnexion(const NetAddr &src) = 0;
|
|
|
|
// Lookup client by key
|
|
Connexion *Lookup(u32 key);
|
|
};
|
|
|
|
|
|
} // namespace sphynx
|
|
|
|
|
|
} // namespace cat
|
|
|
|
#endif // CAT_SPHYNX_SERVER_HPP
|