DEV Community

Red Rad
Red Rad

Posted on

UDP Socket manager for go server-side

I'm working on a prototype online multiplayer game. As you know an online multiplayer game has a server-side process that manages the game state & simulates the world based on the users' inputs.

The game is fast-paced, so I used the UDP protocol to create a virtual channel between the client & the server. The UDP is a stateless protocol, which means that the server & client are not aware of a connection, they only exchange packets. Also, there is no guarantee that the packets are received in order or receive at all.

The Security

To manage the network connections there is a major challenge, security! this challenge has two sides, the first is related to user authentication & session management & the second is secure the content of the packets around the internet.

Session management

Each user input that is received in the server, must be authorized.

Encryption

To secure the content of packets around the internet we need to use encryption.

So I decided to implement a simple general-purpose package for Golang to manage the incoming packets & provide security for the virtual connection between the server & the client.


The udpsocket package

I made this package to make a virtual stateful connection between the client & server using the UDP protocol for a Golang game server (as you know the UDP protocol is stateless, packets may not arrive in order & there is no ACK). check it out on Github

The udpsocket supports a mimic of DTLS handshake, cryptography, session management & authentication. It's responsible to make a secure channel between the client & server on UDP (handshake), authenticate & manage them, decrypt & encrypt the data & provide an API to send or broadcast data to the clients & listen to them.

Custom Implementations

Feels free to:

  • implement your auth client to authorize the users' tokens
  • implement your preferred symmetric & asymmetric encryption algorithms (the RSA & AES encryption are supported as default)
  • implement your preferred encoding package. (the protobuf is supported as default)

Basic Usage

package main

import (
    "context"
    "log"
    "net"
    "fmt"
    "crypto/rsa"

    "demo/auth"
    "demo/encoding"

    "github.com/theredrad/udpsocket"
    "github.com/theredrad/udpsocket/crypto"
)

var (
    pk *rsa.PrivateKey

    udpServerIP   = "127.0.0.1"
    udpServerPort = "7009"

    defaultRSAPrivateKeySize = 2048
)

func main() {
    f, err := auth.NewFirebaseClient(context.Background(), "firebase-config.json") // firebase implementation of auth client to validate firebase-issued tokens
    if err != nil {
        panic(err)
    }

    udpAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%s", udpServerIP, udpServerPort))
    if err != nil {
        panic(err)
    }

    udpConn, err = net.ListenUDP("udp", udpAddr)
    if err != nil {
        panic(err)
    }
    defer udpConn.Close()


    pk, err = crypto.GenerateRSAKey(defaultRSAPrivateKeySize)
    if err != nil {
        panic(err)
    }

    r := crypto.NewRSAFromPK(pk) // creating a new instance of the RSA implementation
    if err != nil {
        panic(err)
    }

    a := crypto.NewAES(crypto.AES_CBC) // creating a new instance of the AES implementation

    t := &encoding.MessagePack{} // an implementation of msgpack for the Transcoder
    s, err := udpsocket.NewServer(udpConn, &udpsocket.Config{
        AuthClient: f,
        Transcoder: t,
        SymmCrypto: a,
        AsymmCrypto: r,
        ReadBufferSize: 2048,
        MinimumPayloadSize: 4,
        ProtocolVersionMajor: 0,
        ProtocolVersionMinor: 1,
    })
    if err != nil {
        panic(err)
    }
    s.SetHandler(incomingHandler)

    go func() { // handling the server errors
        for {
            uerr := <- s.Errors
            if uerr != nil {
                log.Printf("errors on udp server: %s\n", uerr.Error())
            }
        }
    }()

    go s.Serve() // start to run the server, listen to incoming records

  // TODO: need to serve the public key on HTTPS (TLS) to secure the download for the client
}

func incomingHandler(id string, t byte, p []byte) {
    // handle the incoming
}
Enter fullscreen mode Exit fullscreen mode

Dig deeper, How it works

Record

Each message from the client is a Record. The record has a format to parse & decryption.

 1   0   1   1 0 2 52 91 253 115 22 78 39 28 5 192 47 211...
|-| |-| |-|  |------------------------------------------|
 a   b   c                        d

a: record type
b: record protocol major version
c: record protocol minor version
d: record body
Enter fullscreen mode Exit fullscreen mode

Record Types

The first byte of record is the type & indicates how to parse the record. supported reserved types:

  • ClientHello : 1
  • HelloVerify: 2
  • ServerHello: 3
  • Ping: 4
  • Pong: 5

Handshake

The handshake process is made base on a mimic of DTLS protocol, the client sends ClientHello record, this record contains a random bytes & AES key & encrypted by the server public key, which is needed to download on TLS, then the server generates a random cookie based on the client parameters, encrypts it with the client AES key (which is received by ClientHello & sends it as the HelloVerify record. The client decrypts the record & repeats the ClientHello message with the cookie, the record is needed to be encrypted with the server public key, then append the encrypted user token (with AES) to the record body. the server will registers the client after cookie verification & authenticates the user token & then returns a ServerHello record contains a random secret session ID. The handshake process is done here.

      Client                                   Server
      ------                                   ------
      ClientHello           ------>

                            <-----         HelloVerifyRequest
                                           (contains cookie)

      ClientHello           ------>
 (with cookie & token)

                            <-----           ServerHello
                                         (contains session ID)
Enter fullscreen mode Exit fullscreen mode

Encryption

The Server uses both symmetric & asymmetric encryptions to communicate with the client.

The ClientHello record (for the server) contains a secure 256 bit AES key & encrypted by the server public key, so the server could decrypt it with the private key.

The HelloVerify record (for the client) encrypts with the client AES key (which was decrypted before).
If user authentication is required, the client must send the user token with the ClientHello (a record which contains cookie too), but the asymmetric encryption has a limitation on size. for example, the RSA only is able to encrypt data to a maximum amount equal to the key size (e.g. 2048 bits = 256 bytes) & the user token size could be more, so the user token must be encrypted by the client AES, then the server could decrypt it after validation of ClientHello record.
The handshake record structure is a little different because of using hybrid encryption. the two bytes after the protocol version bytes indicates the size of the handshake body which is encrypted by the server public key. the handshake body size is passing because of the key size, the encrypted body size depends on the RSA key size.

 1   0   1   1   0   2 52 91 253 115 22 78 ... 4 22 64 91 195 37 225
|-| |-| |-| |-| |-| |---------------------|   |--------------------|
 a   b   c   d   e            f                         g

a: record type (1 => handshake)
b: record protocol major version (0 => 0.1v)
c: record protocol minor version (1 => 0.1v)
d: handshake body size ([1] 0 => 256 bytes, key size: 2048 bits) (first digit number in base of 256)
e: handshake body size (1 [0] => 256 bytes, key size: 2048 bits) (second digit number in base of 256)
f: handshake body which is encrypted by the server public key & contains the client AES key
g: user token which is encrypted by the client AES key size
Enter fullscreen mode Exit fullscreen mode

Possible issues

If there were any possible issues or needs, please put comments or collaborate to make it better. Repository on the Github

Discussion (0)