Browse Source
Replace net.ResolveUDPAddr with a custom resolveUDPAddr() that: 1. Detects if host is an IP address → uses it directly 2. If it's a domain name → resolves via custom DNS resolver (public DNS servers: 77.88.8.8, 8.8.8.8, 1.1.1.1, etc.) 3. Falls back to system resolver if custom resolver fails 4. Logs: [DNS] resolved example.com → 1.2.3.4 This enables using domain names instead of IP addresses for: - -peer (client): e.g. -peer myserver.com:56000 - -listen (server): e.g. -listen myserver.com:56000 - TURN server (resolved from VK API response) Critical for Android (CGO_ENABLED=0) where the system Go resolver may not work — the custom resolver queries public DNS servers directly.pull/183/head
31 changed files with 823 additions and 19 deletions
@ -0,0 +1,171 @@ |
|||||
|
// SPDX-License-Identifier: MIT
|
||||
|
|
||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"crypto/cipher" |
||||
|
"crypto/rand" |
||||
|
"encoding/binary" |
||||
|
"encoding/hex" |
||||
|
"errors" |
||||
|
"fmt" |
||||
|
"sync/atomic" |
||||
|
|
||||
|
"golang.org/x/crypto/chacha20poly1305" |
||||
|
) |
||||
|
|
||||
|
// Wire format — SRTP-like mimicry:
|
||||
|
//
|
||||
|
// [12B RTP header | 12B explicit nonce | AEAD ciphertext | 16B tag]
|
||||
|
//
|
||||
|
// RTP header (RFC 3550):
|
||||
|
//
|
||||
|
// byte 0: 0x80 V=2, P=0, X=0, CC=0
|
||||
|
// byte 1: 0x6F M=0, PT=111 (opus, typical voice PT)
|
||||
|
// byte 2-3: seq16 BE monotonic, init random
|
||||
|
// byte 4-7: ts32 BE monotonic, init random, increments by 960 (20ms @ 48kHz)
|
||||
|
// byte 8-11: SSRC random per conn, MSB encodes direction
|
||||
|
//
|
||||
|
// 12B explicit nonce = 4B sessionID || 8B counter (BE). sessionID MSB
|
||||
|
// matches SSRC MSB (direction bit). counter starts at a random uint64.
|
||||
|
// AAD = first 24 bytes (RTP header || nonce).
|
||||
|
//
|
||||
|
// VK TURN appears to forward SRTP-shaped ChannelData on a fast path and
|
||||
|
// drop anomalous payloads. AEAD ciphertext + 16B tag is plausible as
|
||||
|
// AES-GCM SRTP per RFC 7714.
|
||||
|
|
||||
|
const ( |
||||
|
wrapKeyLen = 32 |
||||
|
wrapRTPHdrLen = 12 |
||||
|
wrapNonceLen = 12 |
||||
|
wrapTagLen = 16 |
||||
|
wrapHeaderLen = wrapRTPHdrLen + wrapNonceLen // 24
|
||||
|
wrapOverhead = wrapHeaderLen + wrapTagLen // 40
|
||||
|
wrapRTPVersion = 0x80 // V=2, P=0, X=0, CC=0
|
||||
|
wrapRTPPT = 0x6F // M=0, PT=111 (opus)
|
||||
|
wrapTSStep = 960 // 20ms @ 48kHz
|
||||
|
) |
||||
|
|
||||
|
type wrapConn struct { |
||||
|
aead cipher.AEAD |
||||
|
sessionID [4]byte // 4B prefix for nonce; MSB encodes direction
|
||||
|
ssrc [4]byte // SSRC for RTP header; MSB encodes direction
|
||||
|
counter atomic.Uint64 |
||||
|
seq atomic.Uint32 // RTP sequence (used as uint16)
|
||||
|
timestamp atomic.Uint32 // RTP timestamp
|
||||
|
} |
||||
|
|
||||
|
func newWrapConn(key []byte, isServer bool) (*wrapConn, error) { |
||||
|
if len(key) != wrapKeyLen { |
||||
|
return nil, fmt.Errorf("wrap: key must be %d bytes (got %d)", wrapKeyLen, len(key)) |
||||
|
} |
||||
|
aead, err := chacha20poly1305.New(key) |
||||
|
if err != nil { |
||||
|
return nil, fmt.Errorf("wrap: aead init: %w", err) |
||||
|
} |
||||
|
w := &wrapConn{aead: aead} |
||||
|
|
||||
|
var rnd [16]byte |
||||
|
if _, err := rand.Read(rnd[:]); err != nil { |
||||
|
return nil, fmt.Errorf("wrap: rand init: %w", err) |
||||
|
} |
||||
|
copy(w.sessionID[:], rnd[0:4]) |
||||
|
copy(w.ssrc[:], rnd[4:8]) |
||||
|
if isServer { |
||||
|
w.sessionID[0] |= 0x80 |
||||
|
w.ssrc[0] |= 0x80 |
||||
|
} else { |
||||
|
w.sessionID[0] &^= 0x80 |
||||
|
w.ssrc[0] &^= 0x80 |
||||
|
} |
||||
|
w.seq.Store(uint32(binary.BigEndian.Uint16(rnd[8:10]))) |
||||
|
w.timestamp.Store(binary.BigEndian.Uint32(rnd[10:14])) |
||||
|
|
||||
|
var cb [8]byte |
||||
|
if _, err := rand.Read(cb[:]); err != nil { |
||||
|
return nil, fmt.Errorf("wrap: counter rand: %w", err) |
||||
|
} |
||||
|
w.counter.Store(binary.BigEndian.Uint64(cb[:])) |
||||
|
return w, nil |
||||
|
} |
||||
|
|
||||
|
// wrapMaxWire returns max wire bytes for a given payload size.
|
||||
|
func wrapMaxWire(payloadLen int) int { |
||||
|
return wrapOverhead + payloadLen |
||||
|
} |
||||
|
|
||||
|
func (w *wrapConn) wrapInto(dst, payload []byte) (int, error) { |
||||
|
wireLen := wrapOverhead + len(payload) |
||||
|
if len(dst) < wireLen { |
||||
|
return 0, errors.New("wrap: dst buffer too small") |
||||
|
} |
||||
|
|
||||
|
// RTP header.
|
||||
|
dst[0] = wrapRTPVersion |
||||
|
dst[1] = wrapRTPPT |
||||
|
seq := uint16(w.seq.Add(1) - 1) |
||||
|
binary.BigEndian.PutUint16(dst[2:4], seq) |
||||
|
ts := w.timestamp.Add(wrapTSStep) - wrapTSStep |
||||
|
binary.BigEndian.PutUint32(dst[4:8], ts) |
||||
|
copy(dst[8:12], w.ssrc[:]) |
||||
|
|
||||
|
// Explicit nonce.
|
||||
|
noncePos := wrapRTPHdrLen |
||||
|
copy(dst[noncePos:noncePos+4], w.sessionID[:]) |
||||
|
ctr := w.counter.Add(1) - 1 |
||||
|
binary.BigEndian.PutUint64(dst[noncePos+4:noncePos+wrapNonceLen], ctr) |
||||
|
|
||||
|
nonce := dst[noncePos : noncePos+wrapNonceLen] |
||||
|
aad := dst[:wrapHeaderLen] |
||||
|
ctPos := wrapHeaderLen |
||||
|
copy(dst[ctPos:], payload) |
||||
|
w.aead.Seal(dst[ctPos:ctPos], nonce, dst[ctPos:ctPos+len(payload)], aad) |
||||
|
|
||||
|
return wireLen, nil |
||||
|
} |
||||
|
|
||||
|
func (w *wrapConn) unwrapPacket(wire, dst []byte) (int, error) { |
||||
|
if len(wire) < wrapOverhead { |
||||
|
return 0, errors.New("wrap: packet too short") |
||||
|
} |
||||
|
nonce := wire[wrapRTPHdrLen : wrapRTPHdrLen+wrapNonceLen] |
||||
|
aad := wire[:wrapHeaderLen] |
||||
|
ct := wire[wrapHeaderLen:] |
||||
|
|
||||
|
plain, err := w.aead.Open(ct[:0], nonce, ct, aad) |
||||
|
if err != nil { |
||||
|
return 0, fmt.Errorf("wrap: AEAD open: %w", err) |
||||
|
} |
||||
|
if len(plain) > len(dst) { |
||||
|
return 0, errors.New("wrap: dst buffer too small") |
||||
|
} |
||||
|
copy(dst[:len(plain)], plain) |
||||
|
return len(plain), nil |
||||
|
} |
||||
|
|
||||
|
// --- Helpers ---
|
||||
|
|
||||
|
func genWrapKeyHex() (string, error) { |
||||
|
key := make([]byte, wrapKeyLen) |
||||
|
if _, err := rand.Read(key); err != nil { |
||||
|
return "", fmt.Errorf("wrap: key gen: %w", err) |
||||
|
} |
||||
|
return hex.EncodeToString(key), nil |
||||
|
} |
||||
|
|
||||
|
func decodeWrapKey(enabled bool, raw string) ([]byte, error) { |
||||
|
if !enabled { |
||||
|
return nil, nil |
||||
|
} |
||||
|
if raw == "" { |
||||
|
return nil, errors.New("-wrap requires -wrap-key") |
||||
|
} |
||||
|
key, err := hex.DecodeString(raw) |
||||
|
if err != nil { |
||||
|
return nil, fmt.Errorf("-wrap-key invalid hex: %w", err) |
||||
|
} |
||||
|
if len(key) != wrapKeyLen { |
||||
|
return nil, fmt.Errorf("-wrap-key must decode to %d bytes (got %d)", wrapKeyLen, len(key)) |
||||
|
} |
||||
|
return key, nil |
||||
|
} |
||||
@ -0,0 +1,201 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"bytes" |
||||
|
"encoding/binary" |
||||
|
"strings" |
||||
|
"testing" |
||||
|
) |
||||
|
|
||||
|
func TestWrapConnRoundTrip(t *testing.T) { |
||||
|
key := bytes.Repeat([]byte{0x42}, wrapKeyLen) |
||||
|
payload := []byte("dtls record bytes") |
||||
|
|
||||
|
client, err := newWrapConn(key, false) |
||||
|
if err != nil { |
||||
|
t.Fatalf("newWrapConn(client): %v", err) |
||||
|
} |
||||
|
server, err := newWrapConn(key, true) |
||||
|
if err != nil { |
||||
|
t.Fatalf("newWrapConn(server): %v", err) |
||||
|
} |
||||
|
|
||||
|
wire := make([]byte, wrapMaxWire(len(payload))) |
||||
|
n, err := client.wrapInto(wire, payload) |
||||
|
if err != nil { |
||||
|
t.Fatalf("wrapInto: %v", err) |
||||
|
} |
||||
|
wire = wire[:n] |
||||
|
|
||||
|
if wire[0] != wrapRTPVersion { |
||||
|
t.Fatalf("RTP byte0 = 0x%02X, want 0x%02X", wire[0], wrapRTPVersion) |
||||
|
} |
||||
|
if wire[1] != wrapRTPPT { |
||||
|
t.Fatalf("RTP byte1 (PT) = 0x%02X, want 0x%02X", wire[1], wrapRTPPT) |
||||
|
} |
||||
|
if bytes.Contains(wire, payload) { |
||||
|
t.Fatalf("wrapped packet contains plaintext payload") |
||||
|
} |
||||
|
|
||||
|
dst := make([]byte, 1600) |
||||
|
m, err := server.unwrapPacket(wire, dst) |
||||
|
if err != nil { |
||||
|
t.Fatalf("unwrapPacket: %v", err) |
||||
|
} |
||||
|
if m != len(payload) { |
||||
|
t.Fatalf("unwrapped len = %d, want %d", m, len(payload)) |
||||
|
} |
||||
|
if !bytes.Equal(dst[:m], payload) { |
||||
|
t.Fatalf("round trip mismatch: got %q want %q", dst[:m], payload) |
||||
|
} |
||||
|
|
||||
|
// Server → Client
|
||||
|
wire2 := make([]byte, wrapMaxWire(len(payload))) |
||||
|
n2, err := server.wrapInto(wire2, payload) |
||||
|
if err != nil { |
||||
|
t.Fatalf("server wrapInto: %v", err) |
||||
|
} |
||||
|
m2, err := client.unwrapPacket(wire2[:n2], dst) |
||||
|
if err != nil { |
||||
|
t.Fatalf("client unwrapPacket: %v", err) |
||||
|
} |
||||
|
if !bytes.Equal(dst[:m2], payload) { |
||||
|
t.Fatalf("server→client round trip mismatch") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func TestWrapRTPHeaderProgression(t *testing.T) { |
||||
|
key := bytes.Repeat([]byte{0x42}, wrapKeyLen) |
||||
|
wc, err := newWrapConn(key, false) |
||||
|
if err != nil { |
||||
|
t.Fatalf("newWrapConn: %v", err) |
||||
|
} |
||||
|
payload := []byte("x") |
||||
|
|
||||
|
wire1 := make([]byte, wrapMaxWire(len(payload))) |
||||
|
n1, err := wc.wrapInto(wire1, payload) |
||||
|
if err != nil { |
||||
|
t.Fatalf("wrapInto 1: %v", err) |
||||
|
} |
||||
|
wire2 := make([]byte, wrapMaxWire(len(payload))) |
||||
|
n2, err := wc.wrapInto(wire2, payload) |
||||
|
if err != nil { |
||||
|
t.Fatalf("wrapInto 2: %v", err) |
||||
|
} |
||||
|
if n1 != n2 { |
||||
|
t.Fatalf("wire size variance: %d vs %d", n1, n2) |
||||
|
} |
||||
|
|
||||
|
seq1 := binary.BigEndian.Uint16(wire1[2:4]) |
||||
|
seq2 := binary.BigEndian.Uint16(wire2[2:4]) |
||||
|
if seq2 != seq1+1 { |
||||
|
t.Fatalf("seq did not increment: %d → %d", seq1, seq2) |
||||
|
} |
||||
|
|
||||
|
ts1 := binary.BigEndian.Uint32(wire1[4:8]) |
||||
|
ts2 := binary.BigEndian.Uint32(wire2[4:8]) |
||||
|
if ts2-ts1 != wrapTSStep { |
||||
|
t.Fatalf("timestamp step = %d, want %d", ts2-ts1, wrapTSStep) |
||||
|
} |
||||
|
|
||||
|
// SSRC stable across packets.
|
||||
|
if !bytes.Equal(wire1[8:12], wire2[8:12]) { |
||||
|
t.Fatalf("SSRC changed between packets") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func TestWrapDirectionBit(t *testing.T) { |
||||
|
key := bytes.Repeat([]byte{0x42}, wrapKeyLen) |
||||
|
client, err := newWrapConn(key, false) |
||||
|
if err != nil { |
||||
|
t.Fatalf("newWrapConn(client): %v", err) |
||||
|
} |
||||
|
server, err := newWrapConn(key, true) |
||||
|
if err != nil { |
||||
|
t.Fatalf("newWrapConn(server): %v", err) |
||||
|
} |
||||
|
|
||||
|
if client.sessionID[0]&0x80 != 0 { |
||||
|
t.Fatalf("client sessionID MSB should be 0, got 0x%02X", client.sessionID[0]) |
||||
|
} |
||||
|
if server.sessionID[0]&0x80 == 0 { |
||||
|
t.Fatalf("server sessionID MSB should be 1, got 0x%02X", server.sessionID[0]) |
||||
|
} |
||||
|
if client.ssrc[0]&0x80 != 0 { |
||||
|
t.Fatalf("client SSRC MSB should be 0, got 0x%02X", client.ssrc[0]) |
||||
|
} |
||||
|
if server.ssrc[0]&0x80 == 0 { |
||||
|
t.Fatalf("server SSRC MSB should be 1, got 0x%02X", server.ssrc[0]) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func TestDecodeWrapKeyRequiresValidKeyWhenEnabled(t *testing.T) { |
||||
|
if key, err := decodeWrapKey(false, ""); err != nil || key != nil { |
||||
|
t.Fatalf("disabled decodeWrapKey = (%v, %v), want (nil, nil)", key, err) |
||||
|
} |
||||
|
|
||||
|
if _, err := decodeWrapKey(true, ""); err == nil { |
||||
|
t.Fatalf("decodeWrapKey accepted empty key") |
||||
|
} |
||||
|
|
||||
|
shortHex := strings.Repeat("ab", wrapKeyLen-1) |
||||
|
if _, err := decodeWrapKey(true, shortHex); err == nil { |
||||
|
t.Fatalf("decodeWrapKey accepted short key") |
||||
|
} |
||||
|
|
||||
|
fullHex := strings.Repeat("ab", wrapKeyLen) |
||||
|
key, err := decodeWrapKey(true, fullHex) |
||||
|
if err != nil { |
||||
|
t.Fatalf("decodeWrapKey returned error: %v", err) |
||||
|
} |
||||
|
if len(key) != wrapKeyLen { |
||||
|
t.Fatalf("decoded key len = %d, want %d", len(key), wrapKeyLen) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func TestUnwrapRejectsShortPacket(t *testing.T) { |
||||
|
key := bytes.Repeat([]byte{0x42}, wrapKeyLen) |
||||
|
wc, err := newWrapConn(key, false) |
||||
|
if err != nil { |
||||
|
t.Fatalf("newWrapConn: %v", err) |
||||
|
} |
||||
|
if _, err := wc.unwrapPacket([]byte("short"), make([]byte, 16)); err == nil { |
||||
|
t.Fatalf("unwrapPacket accepted short packet") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func TestUnwrapRejectsTamperedPacket(t *testing.T) { |
||||
|
key := bytes.Repeat([]byte{0x42}, wrapKeyLen) |
||||
|
client, err := newWrapConn(key, false) |
||||
|
if err != nil { |
||||
|
t.Fatalf("newWrapConn(client): %v", err) |
||||
|
} |
||||
|
server, err := newWrapConn(key, true) |
||||
|
if err != nil { |
||||
|
t.Fatalf("newWrapConn(server): %v", err) |
||||
|
} |
||||
|
|
||||
|
payload := []byte("integrity test") |
||||
|
wire := make([]byte, wrapMaxWire(len(payload))) |
||||
|
n, err := client.wrapInto(wire, payload) |
||||
|
if err != nil { |
||||
|
t.Fatalf("wrapInto: %v", err) |
||||
|
} |
||||
|
wire = wire[:n] |
||||
|
|
||||
|
// Flip a bit in the ciphertext.
|
||||
|
wire[wrapHeaderLen+1] ^= 0xFF |
||||
|
|
||||
|
dst := make([]byte, 1600) |
||||
|
if _, err := server.unwrapPacket(wire, dst); err == nil { |
||||
|
t.Fatalf("unwrapPacket accepted tampered ciphertext") |
||||
|
} |
||||
|
|
||||
|
// Re-wrap and tamper RTP header (AAD).
|
||||
|
n2, _ := client.wrapInto(wire, payload) |
||||
|
wire = wire[:n2] |
||||
|
wire[8] ^= 0x01 // flip a bit in SSRC
|
||||
|
if _, err := server.unwrapPacket(wire, dst); err == nil { |
||||
|
t.Fatalf("unwrapPacket accepted tampered AAD") |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,198 @@ |
|||||
|
// SPDX-License-Identifier: MIT
|
||||
|
|
||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"crypto/cipher" |
||||
|
"crypto/rand" |
||||
|
"encoding/binary" |
||||
|
"errors" |
||||
|
"fmt" |
||||
|
"net" |
||||
|
"sync" |
||||
|
"sync/atomic" |
||||
|
"time" |
||||
|
|
||||
|
dtlsnet "github.com/pion/dtls/v3/pkg/net" |
||||
|
pionudp "github.com/pion/transport/v4/udp" |
||||
|
"golang.org/x/crypto/chacha20poly1305" |
||||
|
) |
||||
|
|
||||
|
// Wire format is identical to client — see client/wrap.go. Server sets the
|
||||
|
// MSB of sessionID/SSRC; client clears it. RTP header fields are per-conn.
|
||||
|
|
||||
|
const ( |
||||
|
wrapKeyLen = 32 |
||||
|
wrapRTPHdrLen = 12 |
||||
|
wrapNonceLen = 12 |
||||
|
wrapTagLen = 16 |
||||
|
wrapHeaderLen = wrapRTPHdrLen + wrapNonceLen |
||||
|
wrapOverhead = wrapHeaderLen + wrapTagLen |
||||
|
wrapRTPVersion = 0x80 |
||||
|
wrapRTPPT = 0x6F |
||||
|
wrapTSStep = 960 |
||||
|
) |
||||
|
|
||||
|
// bufPool eliminates per-packet heap allocation on the hot read/write paths.
|
||||
|
var bufPool = sync.Pool{ |
||||
|
New: func() any { |
||||
|
b := make([]byte, 1600+wrapOverhead) |
||||
|
return &b |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
// wrapState holds the AEAD instance shared by all connections under one key.
|
||||
|
type wrapState struct { |
||||
|
aead cipher.AEAD |
||||
|
} |
||||
|
|
||||
|
func newWrapState(key []byte) (*wrapState, error) { |
||||
|
if len(key) != wrapKeyLen { |
||||
|
return nil, fmt.Errorf("wrap: key must be %d bytes (got %d)", wrapKeyLen, len(key)) |
||||
|
} |
||||
|
aead, err := chacha20poly1305.New(key) |
||||
|
if err != nil { |
||||
|
return nil, fmt.Errorf("wrap: aead init: %w", err) |
||||
|
} |
||||
|
return &wrapState{aead: aead}, nil |
||||
|
} |
||||
|
|
||||
|
// --- Listener ---
|
||||
|
|
||||
|
func listenWrapped(addr *net.UDPAddr, key []byte) (dtlsnet.PacketListener, error) { |
||||
|
ws, err := newWrapState(key) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
inner, err := pionudp.Listen("udp", addr) |
||||
|
if err != nil { |
||||
|
return nil, fmt.Errorf("wrap: udp listen: %w", err) |
||||
|
} |
||||
|
return &wrapPacketListener{ |
||||
|
inner: dtlsnet.PacketListenerFromListener(inner), |
||||
|
ws: ws, |
||||
|
}, nil |
||||
|
} |
||||
|
|
||||
|
type wrapPacketListener struct { |
||||
|
inner dtlsnet.PacketListener |
||||
|
ws *wrapState |
||||
|
} |
||||
|
|
||||
|
func (l *wrapPacketListener) Accept() (net.PacketConn, net.Addr, error) { |
||||
|
pc, addr, err := l.inner.Accept() |
||||
|
if err != nil { |
||||
|
return pc, addr, err |
||||
|
} |
||||
|
c := &wrapPacketConn{inner: pc, ws: l.ws} |
||||
|
|
||||
|
var rnd [16]byte |
||||
|
if _, err := rand.Read(rnd[:]); err != nil { |
||||
|
return nil, addr, fmt.Errorf("wrap: rand init: %w", err) |
||||
|
} |
||||
|
copy(c.sessionID[:], rnd[0:4]) |
||||
|
copy(c.ssrc[:], rnd[4:8]) |
||||
|
c.sessionID[0] |= 0x80 |
||||
|
c.ssrc[0] |= 0x80 |
||||
|
c.seq.Store(uint32(binary.BigEndian.Uint16(rnd[8:10]))) |
||||
|
c.timestamp.Store(binary.BigEndian.Uint32(rnd[10:14])) |
||||
|
|
||||
|
var cb [8]byte |
||||
|
if _, err := rand.Read(cb[:]); err != nil { |
||||
|
return nil, addr, fmt.Errorf("wrap: counter rand: %w", err) |
||||
|
} |
||||
|
c.counter.Store(binary.BigEndian.Uint64(cb[:])) |
||||
|
return c, addr, nil |
||||
|
} |
||||
|
|
||||
|
func (l *wrapPacketListener) Close() error { return l.inner.Close() } |
||||
|
func (l *wrapPacketListener) Addr() net.Addr { return l.inner.Addr() } |
||||
|
|
||||
|
// --- Per-peer PacketConn ---
|
||||
|
|
||||
|
type wrapPacketConn struct { |
||||
|
inner net.PacketConn |
||||
|
ws *wrapState |
||||
|
sessionID [4]byte |
||||
|
ssrc [4]byte |
||||
|
counter atomic.Uint64 |
||||
|
seq atomic.Uint32 |
||||
|
timestamp atomic.Uint32 |
||||
|
} |
||||
|
|
||||
|
func (c *wrapPacketConn) ReadFrom(p []byte) (int, net.Addr, error) { |
||||
|
bp := bufPool.Get().(*[]byte) //nolint:errcheck // pool New always returns *[]byte
|
||||
|
buf := *bp |
||||
|
need := len(p) + wrapOverhead |
||||
|
if cap(buf) < need { |
||||
|
buf = make([]byte, need) |
||||
|
*bp = buf |
||||
|
} |
||||
|
defer bufPool.Put(bp) |
||||
|
|
||||
|
n, addr, err := c.inner.ReadFrom(buf[:cap(buf)]) |
||||
|
if err != nil { |
||||
|
return 0, addr, err |
||||
|
} |
||||
|
wire := buf[:n] |
||||
|
if len(wire) < wrapOverhead { |
||||
|
return 0, addr, errors.New("wrap: packet too short") |
||||
|
} |
||||
|
nonce := wire[wrapRTPHdrLen : wrapRTPHdrLen+wrapNonceLen] |
||||
|
aad := wire[:wrapHeaderLen] |
||||
|
ct := wire[wrapHeaderLen:] |
||||
|
|
||||
|
plain, err := c.ws.aead.Open(ct[:0], nonce, ct, aad) |
||||
|
if err != nil { |
||||
|
return 0, addr, fmt.Errorf("wrap: AEAD open: %w", err) |
||||
|
} |
||||
|
if len(plain) > len(p) { |
||||
|
return 0, addr, errors.New("wrap: dst buffer too small") |
||||
|
} |
||||
|
copy(p[:len(plain)], plain) |
||||
|
return len(plain), addr, nil |
||||
|
} |
||||
|
|
||||
|
func (c *wrapPacketConn) WriteTo(p []byte, addr net.Addr) (int, error) { |
||||
|
wireLen := wrapOverhead + len(p) |
||||
|
|
||||
|
bp := bufPool.Get().(*[]byte) //nolint:errcheck // pool New always returns *[]byte
|
||||
|
out := *bp |
||||
|
if cap(out) < wireLen { |
||||
|
out = make([]byte, wireLen) |
||||
|
*bp = out |
||||
|
} |
||||
|
out = out[:wireLen] |
||||
|
defer bufPool.Put(bp) |
||||
|
|
||||
|
// RTP header.
|
||||
|
out[0] = wrapRTPVersion |
||||
|
out[1] = wrapRTPPT |
||||
|
seq := uint16(c.seq.Add(1) - 1) |
||||
|
binary.BigEndian.PutUint16(out[2:4], seq) |
||||
|
ts := c.timestamp.Add(wrapTSStep) - wrapTSStep |
||||
|
binary.BigEndian.PutUint32(out[4:8], ts) |
||||
|
copy(out[8:12], c.ssrc[:]) |
||||
|
|
||||
|
noncePos := wrapRTPHdrLen |
||||
|
copy(out[noncePos:noncePos+4], c.sessionID[:]) |
||||
|
ctr := c.counter.Add(1) - 1 |
||||
|
binary.BigEndian.PutUint64(out[noncePos+4:noncePos+wrapNonceLen], ctr) |
||||
|
|
||||
|
nonce := out[noncePos : noncePos+wrapNonceLen] |
||||
|
aad := out[:wrapHeaderLen] |
||||
|
ctPos := wrapHeaderLen |
||||
|
copy(out[ctPos:], p) |
||||
|
c.ws.aead.Seal(out[ctPos:ctPos], nonce, out[ctPos:ctPos+len(p)], aad) |
||||
|
|
||||
|
if _, err := c.inner.WriteTo(out, addr); err != nil { |
||||
|
return 0, err |
||||
|
} |
||||
|
return len(p), nil |
||||
|
} |
||||
|
|
||||
|
func (c *wrapPacketConn) Close() error { return c.inner.Close() } |
||||
|
func (c *wrapPacketConn) LocalAddr() net.Addr { return c.inner.LocalAddr() } |
||||
|
func (c *wrapPacketConn) SetDeadline(t time.Time) error { return c.inner.SetDeadline(t) } |
||||
|
func (c *wrapPacketConn) SetReadDeadline(t time.Time) error { return c.inner.SetReadDeadline(t) } |
||||
|
func (c *wrapPacketConn) SetWriteDeadline(t time.Time) error { return c.inner.SetWriteDeadline(t) } |
||||
Loading…
Reference in new issue