diff --git a/gumble/gumble/client.go b/gumble/gumble/client.go index 0691cda..06a3d54 100644 --- a/gumble/gumble/client.go +++ b/gumble/gumble/client.go @@ -32,7 +32,7 @@ const ( ) // 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. type Client struct { diff --git a/gumble/gumble/udp.go b/gumble/gumble/udp.go index 7289eb7..c10ff6b 100644 --- a/gumble/gumble/udp.go +++ b/gumble/gumble/udp.go @@ -2,10 +2,7 @@ package gumble import ( "bytes" - "encoding/binary" "encoding/hex" - "errors" - "math" "net" "time" @@ -14,12 +11,6 @@ import ( ) 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 = 1024 ) @@ -110,183 +101,3 @@ func (c *Client) sendUDPPing() { 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) -} - - diff --git a/gumble/gumble/udp15.go b/gumble/gumble/udp15.go index fe8907c..95c7859 100644 --- a/gumble/gumble/udp15.go +++ b/gumble/gumble/udp15.go @@ -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. // Returns true if sent, false if TCP should be used. func (c *Client) WriteAudioUDP15(data []byte, final bool) (bool, error) {