Saya membuat paket ini untuk membuat koneksi stateful virtual antara klien & server menggunakan protokol UDP untuk server game golang (seperti yang Anda tahu protokol UDP tidak memiliki kewarganegaraan, paket mungkin tidak sampai secara berurutan & tidak ada ACK).
udpsocket mendukung tiruan jabat tangan DTLS, kriptografi, manajemen sesi & otentikasi. Bertanggung jawab untuk membuat saluran aman antara klien & server di UDP (jabat tangan), mengautentikasi & mengelolanya, mendekripsi & mengenkripsi data & menyediakan API untuk mengirim atau menyiarkan data ke klien & mendengarkannya.
Server udpsocket menerima beberapa parameter untuk memulai:
*net.UDPConnOpsi dapat diteruskan menggunakan fungsi yang telah ditentukan sebelumnya:
udpsocket . WithAuthClient ()
udpsocket . WithTranscoder ()
udpsocket . WithSymmetricCrypto ()
udpsocket . WithAsymmetricCrypto ()
udpsocket . WithReadBufferSize ()
udpsocket . WithMinimumPayloadSize ()
udpsocket . WithProtocolVersion ()
udpsocket . WithHeartbeatExpiration ()
udpsocket . WithLogger () Implementasi antarmuka AuthClient yang digunakan untuk mengautentikasi token pengguna. Jika server Anda tidak memerlukan autentikasi berbasis token, jangan berikan apa pun, sehingga Server akan menggunakan implementasi default yang tidak memiliki autentikasi.
Implementasi antarmuka Transcoder yang digunakan untuk menyandikan & mendekode data antara klien & server. Implementasi default Transcoder adalah Protobuf . Transcoder dapat menyandikan & mendekode beberapa jenis pesan default, seperti jabat tangan & ping & juga mendukung metode umum Marshal & Unmarshal untuk mendukung tipe data khusus Anda.
Implementasi antarmuka crypto.Symmetric untuk mengenkripsi & mendekripsi melalui algoritma kunci Simetris. Implementasi defaultnya menggunakan AES CBC dengan padding PKCS#7 .
Implementasi antarmuka crypto.Asymmetric untuk mengenkripsi & mendekripsi melalui kriptografi kunci publik. Implementasi defaultnya menggunakan RSA dengan ukuran kunci khusus.
ReadBufferSize : Ukuran buffer membacaMinimumPayloadSize : untuk memotong data yang ukurannya tidak mencukupi (untuk mencegah beberapa metode serangan)ProtocolVersionMajor : versi utama protokolProtocolVersionMinor : versi minor protokol package main
import (
"context"
"log"
"net"
"fmt"
"crypto/rsa"
"time"
"os"
"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 . WithAuthClient ( f ),
udpsocket . WithTranscoder ( t ),
udpsocket . WithSymmetricCrypto ( a ),
udpsocket . WithAsymmetricCrypto ( r ),
udpsocket . WithReadBufferSize ( 2048 ),
udpsocket . WithMinimumPayloadSize ( 4 ),
udpsocket . WithProtocolVersion ( 1 , 0 ),
udpsocket . WithHeartbeatExpiration ( 3 * time . Second ),
udpsocket . WithLogger ( log . New ( os . Stdout , "udp server: " , log . Ldate )),
)
if err != nil {
panic ( err )
}
s . SetHandler ( incomingHandler )
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
}Server mengekspor dua metode untuk mengirim pesan ke klien atau menyiarkan pesan ke semua klien. Untuk mengirim pesan ke klien tertentu, Anda harus memiliki ID klien.
Payloadnya adalah pesan yang disandikan Transcoder & dienkripsi ditangani oleh Server.
Penangan adalah fungsi dengan tanda tangan func(id string, t byte, p []byte) . Anda dapat mengatur fungsi handler Anda dengan metode SetHandler . Fungsi ini dipanggil ketika rekaman tipe khusus diterima & diautentikasi. Parameter id adalah ID klien (yang diambil dari token, atau UUID baru yang dihasilkan jika tidak diperlukan otentikasi), parameter t adalah jenis catatan & parameter p adalah payload yang didekripsi, Anda harus Unmarshal ke kustom Anda jenis pesan.
Setiap pesan dari klien adalah Record . Catatan memiliki format untuk diurai & didekripsi.
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
Anda mungkin bertanya mengapa ukuran pesan terenkripsi RSA ditambahkan ke catatan sementara ukuran kunci privat RSA diketahui dan ukuran pesan terenkripsi dapat dihitung. Server sebenarnya menggunakan antarmuka Asymmetric untuk enkripsi dan RSA mengimplementasikannya, jadi mungkin pada implementasi lain, ukuran pesan terenkripsi tidak mengikuti ukuran kunci privat, oleh karena itu kita perlu meneruskan ukuran pesan terenkripsi dalam jabat tangan agar dapat memisahkannya. Bagian terenkripsi AES dan terenkripsi RSA.
Byte pertama dari record adalah tipe & menunjukkan cara mengurai record. tipe cadangan yang didukung:
12345 Proses jabat tangan dibuat berdasarkan meniru protokol DTLS, klien mengirimkan catatan ClientHello , catatan ini berisi byte acak & kunci AES & dienkripsi oleh kunci publik server, yang diperlukan untuk mengunduh di TLS, kemudian server menghasilkan acak cookie berdasarkan parameter klien, mengenkripsinya dengan kunci AES klien (yang diterima oleh ClientHello & mengirimkannya sebagai catatan HelloVerify . Klien mendekripsi catatan & mengulangi pesan ClientHello dengan cookie, catatan tersebut diperlukan untuk dienkripsi dengan kunci publik server, kemudian menambahkan token pengguna terenkripsi (dengan AES) ke badan catatan server akan mendaftarkan klien setelah verifikasi cookie & mengautentikasi token pengguna & kemudian mengembalikan catatan ServerHello yang berisi ID sesi rahasia acak. Proses jabat tangan dilakukan di sini.
Client Server
------ ------
ClientHello ------>
<----- HelloVerifyRequest
(contains cookie)
ClientHello ------>
(with cookie & token)
<----- ServerHello
(contains session ID)
Server menggunakan enkripsi simetris & asimetris untuk berkomunikasi dengan klien.
Catatan ClientHello (untuk server) berisi kunci AES 256 bit yang aman & dienkripsi oleh kunci publik server, sehingga server dapat mendekripsinya dengan kunci pribadi.
Catatan HelloVerify (untuk klien) mengenkripsi dengan kunci AES klien (yang telah didekripsi sebelumnya).
Jika otentikasi pengguna diperlukan, klien harus mengirimkan token pengguna dengan ClientHello (catatan yang juga berisi cookie), tetapi enkripsi asimetris memiliki batasan ukuran. misalnya RSA hanya mampu mengenkripsi data hingga jumlah maksimal sama dengan ukuran kunci (misal 2048 bits = 256 bytes) & ukuran token pengguna bisa lebih, sehingga token pengguna harus dienkripsi oleh AES klien, maka server dapat mendekripsinya setelah validasi catatan ClientHello .
Struktur catatan jabat tangan sedikit berbeda karena menggunakan enkripsi hybrid. dua byte setelah byte versi protokol menunjukkan ukuran badan jabat tangan yang dienkripsi oleh kunci publik server. ukuran badan jabat tangan lewat karena ukuran kunci, ukuran badan terenkripsi bergantung pada ukuran kunci RSA.
1 0 1 1 0 2 52 91 253 115 22 78 39 28 5 192 47 211 ... 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
Setelah jabat tangan berhasil, server mengembalikan SessionID rahasia dalam catatan ServerHello yang dienkripsi oleh Kunci AES klien. Klien harus menambahkan SessionID ke byte rekaman khusus, lalu mengenkripsinya dengan kunci AES, menambahkan header rekaman (tipe rekaman & versi protokol), lalu mengirim byte ke server. Server akan mendekripsi badan rekaman dengan kunci AES klien (yang telah didaftarkan sebelumnya dengan IP & port klien), kemudian mengurai SessionID dari badan yang didekripsi, mengotorisasi ID sesi & meneruskan byte ke fungsi Handler .
Untuk catatan Ping , server segera mengirimkan catatan Pong ke klien.