Align UDP code with udp_encryption.md spec
- Bump ClientVersion from 1.3.0 to 1.5.0 so the server sends protobuf-format MumbleUDP.Audio (type 0x00) instead of legacy format (type 0x80). HandleUDPPacket15 already decodes both. - Remove dead WriteAudioUDP and handleUDPPacket from udp.go. These used the old cryptOut/cryptIn state with counter-based OCB nonces, which was superseded by the 1.5 cryptState15 state in udp15.go. The active paths are WriteAudioUDP15 (outbound) and HandleUDPPacket15 (inbound). - Remove unused udpPacketTypeOpus and udpPingInterval constants. - Clean up stale comment in udp15.go.
This commit is contained in:
committed by
Brandon McGinty
parent
cd32abaaef
commit
e61ebdae38
@@ -32,7 +32,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ClientVersion is the protocol version that Client implements.
|
// ClientVersion is the protocol version that Client implements.
|
||||||
const ClientVersion = 1<<16 | 3<<8 | 0
|
const ClientVersion = 1<<16 | 5<<8 | 0
|
||||||
|
|
||||||
// Client is the type used to create a connection to a server.
|
// Client is the type used to create a connection to a server.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ package gumble
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"math"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -14,12 +11,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// udpPacketTypeOpus is the audio type byte for Opus over UDP.
|
|
||||||
udpPacketTypeOpus = 4
|
|
||||||
|
|
||||||
// udpPingInterval is how often UDP ping packets are sent.
|
|
||||||
udpPingInterval = 5 * time.Second
|
|
||||||
|
|
||||||
// maxUDPPacketSize is the maximum UDP packet size we'll process.
|
// maxUDPPacketSize is the maximum UDP packet size we'll process.
|
||||||
maxUDPPacketSize = 1024
|
maxUDPPacketSize = 1024
|
||||||
)
|
)
|
||||||
@@ -110,183 +101,3 @@ func (c *Client) sendUDPPing() {
|
|||||||
c.udpConn.Write(encrypted)
|
c.udpConn.Write(encrypted)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteAudioUDP writes an encrypted audio packet over UDP.
|
|
||||||
// Returns true if the packet was sent over UDP, false if TCP should be used.
|
|
||||||
var firstUDPSendLogged bool
|
|
||||||
var udpSendCount uint64
|
|
||||||
|
|
||||||
func (c *Client) WriteAudioUDP(format, target byte, sequence int64, final bool, data []byte, X, Y, Z *float32) (bool, error) {
|
|
||||||
if c.udpConn == nil || !c.cryptOut.initialized {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
udpSendCount++
|
|
||||||
|
|
||||||
// Build the unencrypted header
|
|
||||||
var header [1 + varint.MaxVarintLen*2]byte
|
|
||||||
header[0] = (format << 5) | target
|
|
||||||
n := varint.Encode(header[1:], int64(c.Self.Session))
|
|
||||||
if n == 0 {
|
|
||||||
return false, errors.New("gumble: varint out of range")
|
|
||||||
}
|
|
||||||
m := varint.Encode(header[1+n:], sequence)
|
|
||||||
if m == 0 {
|
|
||||||
return false, errors.New("gumble: varint out of range")
|
|
||||||
}
|
|
||||||
headerLen := 1 + n + m
|
|
||||||
|
|
||||||
// Build the unencrypted payload (length varint + opus data + positional)
|
|
||||||
l := int64(len(data))
|
|
||||||
if final {
|
|
||||||
l |= 0x2000
|
|
||||||
}
|
|
||||||
var payload [varint.MaxVarintLen]byte
|
|
||||||
p := varint.Encode(payload[:], l)
|
|
||||||
if p == 0 {
|
|
||||||
return false, errors.New("gumble: varint out of range")
|
|
||||||
}
|
|
||||||
|
|
||||||
positionalLen := 0
|
|
||||||
if X != nil {
|
|
||||||
positionalLen = 3 * 4
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combine payload for encryption: length varint + opus data + positional
|
|
||||||
plainPayload := make([]byte, p+len(data)+positionalLen)
|
|
||||||
copy(plainPayload, payload[:p])
|
|
||||||
copy(plainPayload[p:], data)
|
|
||||||
if positionalLen > 0 {
|
|
||||||
binary.LittleEndian.PutUint32(plainPayload[p+len(data):], math.Float32bits(*X))
|
|
||||||
binary.LittleEndian.PutUint32(plainPayload[p+len(data)+4:], math.Float32bits(*Y))
|
|
||||||
binary.LittleEndian.PutUint32(plainPayload[p+len(data)+8:], math.Float32bits(*Z))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt the payload
|
|
||||||
c.cryptOut.mu.Lock()
|
|
||||||
counter := c.cryptOut.counter
|
|
||||||
c.cryptOut.counter++
|
|
||||||
nonce := c.cryptOut.nonceForPacket(counter)
|
|
||||||
c.cryptOut.mu.Unlock()
|
|
||||||
|
|
||||||
log.Info("UDP send #%d: seq=%d counter=%d nonce=%s plain_len=%d opus_len=%d final=%v",
|
|
||||||
udpSendCount, sequence, counter, hex.EncodeToString(nonce[:]),
|
|
||||||
len(plainPayload), len(data), final)
|
|
||||||
|
|
||||||
encrypted, err := c.cryptOut.encrypt(counter, plainPayload)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("UDP send #%d: encrypt FAILED: %v", udpSendCount, err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send: header (plain) + encrypted payload
|
|
||||||
packet := make([]byte, headerLen+len(encrypted))
|
|
||||||
copy(packet, header[:headerLen])
|
|
||||||
copy(packet[headerLen:], encrypted)
|
|
||||||
|
|
||||||
log.Info("UDP send #%d: header_len=%d enc_len=%d total=%d hex=%s",
|
|
||||||
udpSendCount, headerLen, len(encrypted), len(packet),
|
|
||||||
hex.EncodeToString(packet))
|
|
||||||
|
|
||||||
_, err = c.udpConn.Write(packet)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("UDP send #%d: write FAILED: %v", udpSendCount, err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleUDPPacket processes an incoming UDP audio packet.
|
|
||||||
func (c *Client) handleUDPPacket(packet []byte, pktNum uint64) {
|
|
||||||
if len(packet) < 1 {
|
|
||||||
log.Warn("UDP #%d: empty packet, ignoring", pktNum)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
typeByte := packet[0]
|
|
||||||
audioType := (typeByte >> 5) & 0x7
|
|
||||||
target := typeByte & 0x1F
|
|
||||||
|
|
||||||
log.Info("UDP #%d: type_byte=0x%02x audio_type=%d target=%d pkt_len=%d",
|
|
||||||
pktNum, typeByte, audioType, target, len(packet))
|
|
||||||
|
|
||||||
// Skip ping packets (type 0, 1, or 2 in bits 5-7 = type 0)
|
|
||||||
if audioType != udpPacketTypeOpus {
|
|
||||||
log.Info("UDP #%d: skipping non-Opus packet (audio_type=%d)", pktNum, audioType)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the user by session (in plaintext header)
|
|
||||||
buf := packet[1:]
|
|
||||||
session, n := varint.Decode(buf)
|
|
||||||
if n <= 0 {
|
|
||||||
log.Warn("UDP #%d: failed to decode session varint (buf_len=%d first_byte=0x%02x)",
|
|
||||||
pktNum, len(buf), buf[0])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buf = buf[n:]
|
|
||||||
|
|
||||||
// Parse sequence for decryption nonce
|
|
||||||
seq, m := varint.Decode(buf)
|
|
||||||
if m <= 0 {
|
|
||||||
log.Warn("UDP #%d: failed to decode seq varint (buf_len=%d)", pktNum, len(buf))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
headerLen := 1 + n + m
|
|
||||||
encrypted := packet[headerLen:]
|
|
||||||
|
|
||||||
log.Info("UDP #%d: session=%d seq=%d header_len=%d encrypted_len=%d",
|
|
||||||
pktNum, session, seq, headerLen, len(encrypted))
|
|
||||||
|
|
||||||
user := c.Users[uint32(session)]
|
|
||||||
if user == nil {
|
|
||||||
log.Warn("UDP #%d: unknown session %d (known sessions: %d)",
|
|
||||||
pktNum, session, len(c.Users))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("UDP #%d: user=%s", pktNum, user.Name)
|
|
||||||
|
|
||||||
// Decrypt the payload
|
|
||||||
if !c.cryptIn.initialized {
|
|
||||||
log.Warn("UDP #%d: cryptIn not initialized, dropping", pktNum)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cryptIn.mu.Lock()
|
|
||||||
counter := c.cryptIn.counter
|
|
||||||
c.cryptIn.counter++
|
|
||||||
nonce := c.cryptIn.nonceForPacket(counter)
|
|
||||||
c.cryptIn.mu.Unlock()
|
|
||||||
|
|
||||||
log.Info("UDP #%d: decrypting with counter=%d nonce=%s encrypted_hex=%s",
|
|
||||||
pktNum, counter, hex.EncodeToString(nonce[:]), hex.EncodeToString(encrypted))
|
|
||||||
|
|
||||||
plaintext, err := c.cryptIn.decrypt(counter, encrypted)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("UDP #%d: decrypt FAILED for %s (counter=%d): %v",
|
|
||||||
pktNum, user.Name, counter, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("UDP #%d: decrypt OK, plaintext_len=%d hex=%s",
|
|
||||||
pktNum, len(plaintext), hex.EncodeToString(plaintext))
|
|
||||||
|
|
||||||
// Now feed the decrypted payload through the existing TCP audio handler.
|
|
||||||
// We reconstruct the full UDPTunnel format: type byte + session + seq + payload.
|
|
||||||
// The handleUDPTunnel expects: [type/target byte] [session varint] [seq varint] [length varint] [data] [pos]
|
|
||||||
// Our decrypted payload is: [length varint] [data] [pos]
|
|
||||||
// We need to prepend the type byte, session, and seq.
|
|
||||||
fullPacket := make([]byte, 1+n+m+len(plaintext))
|
|
||||||
fullPacket[0] = packet[0]
|
|
||||||
varint.Encode(fullPacket[1:], session)
|
|
||||||
varint.Encode(fullPacket[1+n:], seq)
|
|
||||||
copy(fullPacket[1+n+m:], plaintext)
|
|
||||||
|
|
||||||
log.Info("UDP #%d: dispatching to handleUDPTunnel (fullPacket_len=%d)",
|
|
||||||
pktNum, len(fullPacket))
|
|
||||||
|
|
||||||
c.handleUDPTunnel(fullPacket)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -478,9 +478,6 @@ func nextFrameNumber() uint32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Replace the WriteAudioUDP path for 1.5 native format.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// WriteAudioUDP15 writes an encrypted audio packet using Mumble 1.5 native UDP.
|
// WriteAudioUDP15 writes an encrypted audio packet using Mumble 1.5 native UDP.
|
||||||
// Returns true if sent, false if TCP should be used.
|
// Returns true if sent, false if TCP should be used.
|
||||||
func (c *Client) WriteAudioUDP15(data []byte, final bool) (bool, error) {
|
func (c *Client) WriteAudioUDP15(data []byte, final bool) (bool, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user