このパッケージは、Golang ゲームサーバーの UDP プロトコルを使用してクライアントとサーバーの間に仮想ステートフル接続を作成するために作成しました (ご存知のとおり、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
です。トランスコーダは、ハンドシェイクや ping などのいくつかのデフォルトのメッセージ タイプをエンコードおよびデコードでき、カスタム データ タイプをサポートするための一般的なMarshal
およびUnmarshal
メソッドもサポートします。
対称キー アルゴリズムを介して暗号化および復号化するためのcrypto.Symmetric
インターフェイスの実装。デフォルトの実装では、 PKCS#7
パディングを備えたAES CBC
が使用されます。
公開キー暗号化を介して暗号化および復号化するための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
}
サーバーは、クライアントにメッセージを送信するか、すべてのクライアントにメッセージをブロードキャストするための 2 つのメソッドをエクスポートしました。特定のクライアントにメッセージを送信するには、クライアント ID が必要です。
ペイロードはトランスコーダでエンコードされたメッセージであり、暗号化はサーバーによって処理されます。
ハンドラーはfunc(id string, t byte, p []byte)
シグネチャを持つ関数です。 SetHandler
メソッドを使用してハンドラー関数を設定できます。この関数は、カスタム タイプのレコードが受信および認証されたときに呼び出されます。 id
パラメータはクライアント 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 でダウンロードするために必要なサーバーの公開キーによって暗号化され、サーバーはランダムなバイトを生成します。 Cookie はクライアント パラメータに基づいて作成され、クライアント AES キーで暗号化されます (これはClientHello
によって受信され、 HelloVerify
レコードとして送信されます。クライアントはレコードを復号化し、 ClientHello
メッセージをCookie を使用する場合は、レコードをサーバーの公開キーで暗号化する必要があります。その後、暗号化されたユーザー トークン (AES を使用) をレコード本体に追加します。サーバーは、Cookie の検証とユーザー トークンの認証後にクライアントを登録し、 ServerHello
レコードを返します。ランダムなシークレット セッション ID が含まれます。ハンドシェイク プロセスはここで行われます。
Client Server
------ ------
ClientHello ------>
<----- HelloVerifyRequest
(contains cookie)
ClientHello ------>
(with cookie & token)
<----- ServerHello
(contains session ID)
Server
対称暗号化と非対称暗号化の両方を使用してクライアントと通信します。
ClientHello
レコード (サーバー用) には、サーバーの公開キーによって暗号化された安全な 256 ビット AES キーが含まれているため、サーバーは秘密キーを使用してそれを復号化できます。
HelloVerify
レコード (クライアント用) は、クライアント AES キー (以前に復号化されている) を使用して暗号化されます。
ユーザー認証が必要な場合、クライアントはClientHello
(Cookie も含むレコード) を使用してユーザー トークンを送信する必要がありますが、非対称暗号化にはサイズ制限があります。たとえば、RSA はキー サイズ (例: 2048 ビット = 256 バイト) に等しい最大量までしかデータを暗号化できません。ユーザー トークン サイズはそれ以上になる可能性があるため、ユーザー トークンはクライアント AES によって暗号化される必要があります。サーバーは、 ClientHello
レコードの検証後にそれを復号化できます。
ハイブリッド暗号化を使用しているため、ハンドシェイク レコードの構造は少し異なります。プロトコル バージョン バイトの後の 2 バイトは、サーバーの公開キーによって暗号化されるハンドシェイク本体のサイズを示します。ハンドシェイク本体のサイズはキーのサイズによって決まりますが、暗号化された本体のサイズは 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
ハンドシェイクが成功すると、サーバーはクライアントの AES キーによって暗号化された秘密のSessionID
ServerHello
レコードで返します。クライアントはカスタム レコード バイトにSessionID
追加し、AES キーで暗号化し、レコード ヘッダー (レコード タイプとプロトコル バージョン) を追加して、バイトをサーバーに送信する必要があります。サーバーはクライアント AES キー (クライアントの IP とポートで事前に登録されている) によってレコード本文を復号化し、復号化された本文からSessionID
を解析し、セッション ID を認証してバイトをHandler
関数に渡します。
Ping
レコードの場合、サーバーは即座にPong
レコードをクライアントに送信します。