Best practices for building secure single socket UDP listener server?

I’m in the last steps of building a multiplayer game and I would like to get constructive criticism on how I am building my single socket listener server. I’m rather new to designing servers so please let me know if anything I am doing is incorrect or the naive approach.

Currently I am encrypted all my session traffic via OpenSSL’s DTLS, so every single establishes connection has a unique instance of the following struct:



struct clientInfo {
    int32 uID;    //Unique player ID that is stored in SQL database
    SSL *con;   //DTLS specific struct that retains the connection and encryption data for the client
    BIO *rbio;   //read buffer for storing encrypted data
}


My understanding is this is the near bare minimum needed for maintaining a encrypted session. Behind the scenes I pruned the bio and ssl structs to get rid of fields that are for TLS only, but not sure if there is more I could prune, any information regarding that would be much appreciated.

The packet processing itself is mainly done in load balanced working threads which maintain 64 connects per thread; However I am trying to have a single non-blocking listening socket that routes all traffic to the working threads. One of my concern related to this listening server is DDOS attacks and minimizing their effectiveness. My current priority is try to ensure the packet is valid before the data is encrypted, but to do so that means I have the client append the players unique identifier (in this situation represented by a unique uint32 stored in the SQL DB) and a uint16 that defines the type of data that is in the (potentially) encrypted packet. Since I can’t have this data encrypted it is currently in plain-text header that resides on top of the encrypted payload. I don’t like exposing such data, but I cannot think of a better way and would appreciate criticism specific to this regard.

below is pseudo code of my current idea for my listener socket. Is this approach good? Is it naive? Any specific flaws or recommended improvements? It is heavily commented to explain functions used.

note: for brevity this code excludes account create requests and assumes that legit clients are aware of their user ID () prior to connection.



listenloop(ListenSocket) {
    inBytes = recvfrom(ListenSocket, inBuff, 1400, 0, inAddr, &inAddrLen)
    if (inBytes>4) {                        //if packet is smaller than 32 bits it is garbage. trash it.
        if (isAddrBanned(inAddr)) { goto end; }    //addr is banned, trash packet

         = scrapeUID(inBuff);            //get the plaintext user ID.
        if (!isValidID(uID)) { goto end; }        //SQL query found User doesnt exist, trash packet. TODO: ban addr after too many failed attempts
        if (isIDBanned(uID)) { goto end; }        //SQL query found User is banned, trash packet. TODO: send packet back letting user know they are banned.

        uint16 packetType = getPacketType(*inBuff[0], *inBuff[1]);     //valid packet types are arbitrary numbers to reduce chance of garbage getting further.

        switch (packetType) { //switch on packetType, ordered by most common type first.

        case 33333: {    //If client claims to have established

            if (!sendToWorkingThread(*inBuff, getWorkingThread(uID))) { goto end; }    //Find working thread uID's session belongs to and send the buffer to it, unless the thread wasn't found in which we trash the packet.    
        }

        case 44444: {    //If client claims to be in middle of handshake

            if (sessionExists(uID)) { goto end; }    //User already has a session, trash packet. TODO: validate the session was abandoned and allow client to rejoin.
            continueDTLSHandshake(uID, *inBuff);    //continue handshake, based heavily off dtlv1_listen() function. Once a secure connection is established the client will send a hashed password to the server            
        }

        case 55555: {     //If client claims to need a new connection

            if (sessionExists(uID)) { goto end; }    //User already has a session, trash packet. TODO: validate the session was abandoned and allow client to rejoin.

            clientInfo newClient;                        //create new struct
            newClient.con = SSL_new(ctx);                    //client specific SSL struct
            newClient.rbio = BIO_new(BIO_s_mem());            //client specific write buffer
            BIO_set_mem_eof_return(newClient.rbio, -1);        //make sure buffer is set correctly
            SSL_set_bio(newClient.con, newClient.rbio, newClient.rbio);                //assign buffer to SSL struct
            BIO_write(newClient.rbio, inBuff, ListenSocket);//move packet payload to client specific buffer

            pendingClients.Add(newClient);                //add to UE4 TArray of pending connections.
            startDTLSHandshake(newClient);                //begin new handshake. Based heavily off dtlsv1_listen() function to retain cookie check.
        }
        default: { goto end; } //packet is corrupted or not from client, trash it                            
        }
    }
end:
    trashPacket();                //get that weak **** out of here
    doWhateverCleanUpIsNeed();  //a clean memory is a happy memory
}