나는 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
입니다. 트랜스코더는 핸드셰이크 및 핑과 같은 일부 기본 메시지 유형을 인코딩 및 디코딩할 수 있으며 사용자 정의 데이터 유형을 지원하기 위해 일반 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
}
서버는 클라이언트에 메시지를 보내거나 모든 클라이언트에 메시지를 브로드캐스트하는 두 가지 방법을 내보냈습니다. 특정 클라이언트에게 메시지를 보내려면 클라이언트 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에서 다운로드하는 데 필요한 서버 공개 키로 암호화된 다음 서버는 무작위 바이트를 생성합니다. 클라이언트 매개변수를 기반으로 쿠키를 클라이언트 AES 키( ClientHello
에서 수신하여 HelloVerify
레코드로 전송)로 암호화합니다. 클라이언트는 레코드를 해독하고 쿠키로 ClientHello
메시지를 반복합니다. 레코드는 다음을 사용하여 암호화해야 합니다. 서버 공개 키를 사용하고 암호화된 사용자 토큰(AES 포함)을 레코드 본문에 추가합니다. 서버는 쿠키 확인 후 클라이언트를 등록하고 사용자 토큰을 인증한 다음 임의의 비밀 세션 ID가 포함된 ServerHello
레코드를 반환합니다. 여기서 완료되었습니다.
Client Server
------ ------
ClientHello ------>
<----- HelloVerifyRequest
(contains cookie)
ClientHello ------>
(with cookie & token)
<----- ServerHello
(contains session ID)
Server
대칭 및 비대칭 암호화를 모두 사용하여 클라이언트와 통신합니다.
ClientHello
레코드(서버용)에는 보안 256비트 AES 키가 포함되어 있으며 서버 공개 키로 암호화되어 있으므로 서버는 개인 키로 이를 해독할 수 있습니다.
HelloVerify
레코드(클라이언트용)는 클라이언트 AES 키(이전에 해독된)로 암호화됩니다.
사용자 인증이 필요한 경우 클라이언트는 ClientHello
(쿠키도 포함하는 레코드)와 함께 사용자 토큰을 보내야 하지만 비대칭 암호화에는 크기에 제한이 있습니다. 예를 들어 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 키로 암호화된 ServerHello
레코드에 비밀 SessionID
를 반환합니다. 클라이언트는 사용자 정의 레코드 바이트에 SessionID
를 추가한 다음 AES 키로 암호화하고 레코드 헤더(레코드 유형 및 프로토콜 버전)를 추가한 다음 바이트를 서버로 보내야 합니다. 서버는 클라이언트 AES 키(클라이언트의 IP 및 포트로 이전에 등록됨)로 레코드 본문을 해독한 다음 해독된 본문에서 SessionID
구문 분석하고 세션 ID를 승인한 후 바이트를 Handler
함수에 전달합니다.
Ping
레코드의 경우 서버는 즉시 Pong
레코드를 클라이언트에 보냅니다.