لقد قمت بإعداد هذه الحزمة لإجراء اتصال افتراضي ذي حالة بين العميل والخادم باستخدام بروتوكول UDP لخادم ألعاب golang (كما تعلم فإن بروتوكول UDP عديم الحالة، وقد لا تصل الحزم بالترتيب ولا يوجد ACK).
يدعم udpsocket
محاكاة مصافحة DTLS والتشفير وإدارة الجلسة والمصادقة. إنها مسؤولة عن إنشاء قناة آمنة بين العميل والخادم على UDP (المصافحة)، والمصادقة عليها وإدارتها، وفك تشفير البيانات وتشفيرها وتوفير واجهة برمجة التطبيقات (API) لإرسال أو بث البيانات إلى العملاء والاستماع إليهم.
يقبل خادم udpsocket
بعض المعلمات للبدء:
*net.UDPConn
يمكن تمرير الخيارات باستخدام وظائف محددة مسبقًا:
udpsocket . WithAuthClient ()
udpsocket . WithTranscoder ()
udpsocket . WithSymmetricCrypto ()
udpsocket . WithAsymmetricCrypto ()
udpsocket . WithReadBufferSize ()
udpsocket . WithMinimumPayloadSize ()
udpsocket . WithProtocolVersion ()
udpsocket . WithHeartbeatExpiration ()
udpsocket . WithLogger ()
أحد تطبيقات واجهة AuthClient
المستخدمة لمصادقة الرمز المميز للمستخدم. إذا كان الخادم الخاص بك لا يحتاج إلى أي مصادقة قائمة على الرمز المميز، فلا تمرر أي شيء، لذلك سيستخدم الخادم التنفيذ الافتراضي له والذي لا يحتوي على مصادقة.
تطبيق لواجهة Transcoder
المستخدمة لتشفير وفك تشفير البيانات بين العميل والخادم. التطبيق الافتراضي لـ Transcoder
هو Protobuf
. يمكن لبرنامج Transcoder تشفير وفك تشفير بعض أنواع الرسائل الافتراضية، مثل المصافحة واختبار الاتصال، كما يدعم أيضًا أساليب Marshal
و Unmarshal
العامة لدعم نوع البيانات المخصص الخاص بك.
تطبيق واجهة crypto.Symmetric
للتشفير وفك التشفير عبر خوارزميات المفاتيح المتماثلة. يستخدم التنفيذ الافتراضي AES CBC
مع حشوة PKCS#7
.
تطبيق واجهة crypto.Asymmetric
للتشفير وفك التشفير عبر تشفير المفتاح العام. يستخدم التنفيذ الافتراضي RSA
بحجم مفتاح مخصص.
ReadBufferSize
: حجم المخزن المؤقت للقراءةMinimumPayloadSize
: لقطع البيانات التي ليس لها حجم كافٍ (لمنع بعض طرق الهجوم)ProtocolVersionMajor
: الإصدار الرئيسي للبروتوكولProtocolVersionMinor
: الإصدار الثانوي للبروتوكول 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
}
قام الخادم بتصدير طريقتين لإرسال رسالة إلى العميل أو بث رسالة لجميع العملاء. لإرسال رسالة إلى عميل معين، يجب أن يكون لديك معرف العميل.
الحمولة عبارة عن رسالة مشفرة بواسطة Transcoder ويتم معالجتها بواسطة الخادم.
المعالج عبارة عن دالة ذات توقيع func(id string, t byte, p []byte)
. يمكنك ضبط وظيفة المعالج الخاص بك عن طريق طريقة SetHandler
. يتم استدعاء هذه الوظيفة عند استلام سجل نوع مخصص ومصادقته. المعلمة id
هي معرف العميل (الذي يتم جلبه من الرمز المميز، أو UUID الجديد الذي تم إنشاؤه إذا لم تكن المصادقة مطلوبة)، والمعلمة t
هي نوع السجل والمعلمة p
هي الحمولة النافعة التي تم فك تشفيرها، ويجب عليك Unmarshal
حسب احتياجاتك نوع الرسالة.
كل رسالة من العميل عبارة عن Record
. يحتوي السجل على تنسيق للتحليل وفك التشفير.
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
قد تتساءل عن سبب إضافة حجم الرسالة المشفرة RSA إلى السجل بينما يكون حجم المفتاح الخاص RSA معروفًا وحجم الرسالة المشفرة قابلاً للحساب. يستخدم Server
في الواقع واجهة Asymmetric
للتشفير ويقوم RSA بتنفيذها، لذلك ربما في التطبيقات الأخرى، لا يتبع حجم الرسالة المشفرة حجم المفتاح الخاص، وبالتالي نحتاج إلى تمرير حجم الرسالة المشفرة في المصافحة حتى نتمكن من فصل أقسام مشفرة AES و RSA مشفرة.
البايت الأول من السجل هو النوع ويشير إلى كيفية تحليل السجل. الأنواع المحجوزة المدعومة:
1
2
3
4
5
تتم عملية المصافحة بناءً على محاكاة بروتوكول DTLS، حيث يرسل العميل سجل ClientHello
، ويحتوي هذا السجل على بايت عشوائي ومفتاح AES ومشفر بواسطة المفتاح العام للخادم، وهو مطلوب للتنزيل على TLS، ثم يقوم الخادم بإنشاء عشوائي ملف تعريف الارتباط استنادًا إلى معلمات العميل، يقوم بتشفيره باستخدام مفتاح AES للعميل (الذي يستقبله ClientHello
ويرسله كسجل HelloVerify
. يقوم العميل بفك تشفير السجل وتكرار رسالة ClientHello
مع ملف تعريف الارتباط، يلزم تشفير السجل باستخدام المفتاح العام للخادم، ثم إلحاق رمز المستخدم المشفر (مع AES) بنص السجل، وسيقوم الخادم بتسجيل العميل بعد التحقق من ملف تعريف الارتباط والمصادقة على الرمز المميز للمستخدم ثم إرجاع سجل ServerHello
يحتوي على معرف جلسة سري عشوائي. تتم عملية المصافحة هنا.
Client Server
------ ------
ClientHello ------>
<----- HelloVerifyRequest
(contains cookie)
ClientHello ------>
(with cookie & token)
<----- ServerHello
(contains session ID)
يستخدم Server
التشفيرات المتماثلة وغير المتماثلة للتواصل مع العميل.
يحتوي سجل ClientHello
(للخادم) على مفتاح AES آمن 256 بت ومشفر بواسطة المفتاح العام للخادم، بحيث يمكن للخادم فك تشفيره باستخدام المفتاح الخاص.
يتم تشفير سجل HelloVerify
(للعميل) باستخدام مفتاح AES للعميل (الذي تم فك تشفيره من قبل).
إذا كانت مصادقة المستخدم مطلوبة، فيجب على العميل إرسال رمز المستخدم المميز مع ClientHello
(سجل يحتوي على ملف تعريف الارتباط أيضًا)، ولكن التشفير غير المتماثل له قيود على الحجم. على سبيل المثال، RSA فقط قادر على تشفير البيانات إلى حد أقصى يساوي حجم المفتاح (على سبيل المثال 2048 بت = 256 بايت) ويمكن أن يكون حجم الرمز المميز للمستخدم أكبر، لذلك يجب أن يكون الرمز المميز للمستخدم مشفرًا بواسطة العميل AES، ثم يمكن للخادم فك تشفيره بعد التحقق من صحة سجل ClientHello
.
تختلف بنية سجل المصافحة قليلاً بسبب استخدام التشفير المختلط. تشير البايتتان بعد بايتات إصدار البروتوكول إلى حجم نص المصافحة المشفر بواسطة المفتاح العام للخادم. حجم جسم المصافحة يمر بسبب حجم المفتاح، وحجم الجسم المشفر يعتمد على حجم مفتاح 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
بعد المصافحة الناجحة، يقوم الخادم بإرجاع SessionID
السري في سجل ServerHello
والذي يتم تشفيره بواسطة مفتاح AES للعميل. يجب على العميل إلحاق SessionID
ببايتات السجل المخصصة، ثم تشفيره بواسطة مفتاح AES، وإضافة رؤوس السجل (نوع السجل وإصدار البروتوكول)، ثم إرسال البايتات إلى الخادم. سيقوم الخادم بفك تشفير نص السجل بواسطة مفتاح AES للعميل (الذي تم تسجيله من قبل باستخدام IP ومنفذ العميل)، ثم تحليل SessionID
من النص الذي تم فك تشفيره، وتخويل معرف الجلسة وتمرير البايتات إلى وظيفة Handler
.
بالنسبة لسجل Ping
، يرسل الخادم سجل Pong
إلى العميل بشكل فوري.