Documentation
¶
Index ¶
- Constants
- Variables
- func FingerprintSHA256(certDER []byte) string
- func IsDTLSPacket(b byte) bool
- func IsRTPPacket(b byte) bool
- func SelfSignedDTLSCert(notAfter time.Time) ([]byte, *ecdsa.PrivateKey, error)
- type DTLSEndpoint
- type DTLSResult
- type RTCPStats
- type RTPHeader
- type RTPPacket
- type SIPRTPTransport
- func (t *SIPRTPTransport) Attach(s *media.MediaSession)
- func (t *SIPRTPTransport) Close() error
- func (t *SIPRTPTransport) Codec() media.CodecConfig
- func (t *SIPRTPTransport) Next(ctx context.Context) (media.MediaPacket, error)
- func (t *SIPRTPTransport) Send(ctx context.Context, packet media.MediaPacket) (int, error)
- func (t *SIPRTPTransport) String() string
- func (t *SIPRTPTransport) WakeupRead()
- type SRTPKeys
- type SRTPProfile
- type Session
- func (s *Session) AddMirrorRemote(addr *net.UDPAddr, ttl time.Duration)
- func (s *Session) Close() error
- func (s *Session) EnableDTLSSRTP(rx, tx *srtp.Context) error
- func (s *Session) EnableSDESSRTP(peerOutboundKey, peerOutboundSalt, localOutboundKey, localOutboundSalt []byte) error
- func (s *Session) EnableSDESSRTPWithProfile(prof srtp.ProtectionProfile, ...) error
- func (s *Session) FirstPacket() <-chan struct{}
- func (s *Session) RTCPSnapshot() RTCPStats
- func (s *Session) ReceiveRTP(buffer []byte) (n int, from *net.UDPAddr, packet *RTPPacket, err error)
- func (s *Session) SendRTP(payload []byte, payloadType uint8, samples uint32) error
- func (s *Session) SetRTCPClockRate(hz uint32)
- func (s *Session) SetRemoteAddr(addr *net.UDPAddr)
- func (s *Session) StartDTLS(ctx context.Context, asServer bool, certDER []byte, key *ecdsa.PrivateKey, ...) <-chan DTLSResult
- func (s *Session) StatsSnapshot() SessionStats
- type SessionStats
Constants ¶
const DefaultJitterPlaybackDelay = 80 * time.Millisecond
DefaultJitterPlaybackDelay is the default inbound playout delay (reorder + smoothing).
Variables ¶
var ErrRTPDiscard = errors.New("rtp: discard packet")
ErrRTPDiscard signals that a datagram was read but must be ignored (malformed RTP / policy). Callers that loop on ReceiveRTP should continue without treating it as a transport failure.
Functions ¶
func FingerprintSHA256 ¶
FingerprintSHA256 computes the SDP `a=fingerprint:sha-256 HEX` digest for a DER-encoded cert. Returns the colon-separated uppercase hex form (32 bytes → 95-char string).
func IsDTLSPacket ¶
IsDTLSPacket reports whether the first byte of a UDP datagram on a multiplexed RTP socket indicates DTLS (RFC 5764 §5.1.2). The type-byte ranges are:
- 0..3 STUN
- 16..19 ZRTP
- 20..63 DTLS ← us
- 64..79 TURN channel
- 128..191 RTP / RTCP
Ranges are disjoint by design so a demux can route by first byte.
func IsRTPPacket ¶
IsRTPPacket reports whether the first byte indicates RTP/RTCP. Useful for the muxer's "default route" check.
func SelfSignedDTLSCert ¶
SelfSignedDTLSCert generates a fresh ECDSA P-256 cert for one DTLS endpoint. RFC 5763 §6.5 says the cert SHOULD be self-signed because trust comes from the SDP fingerprint, not a CA — minting per-call certs reduces compromise blast radius.
notAfter defaults to 30 days when zero. We don't need to rotate the cert often because each call brings its own.
Types ¶
type DTLSEndpoint ¶
type DTLSEndpoint struct {
// contains filtered or unexported fields
}
DTLSEndpoint wraps a single DTLS-SRTP handshake on a UDP socket. Construct via NewDTLSEndpoint, run Handshake (server or client), then call SRTPKeys to get the derived material. Close releases the underlying conn.
This is the standalone form — when integrating with Session, we pass a `net.Conn` that's actually a DTLS-only multiplexed view of the shared RTP socket (built in dtls_session.go, slice A-3).
func NewDTLSEndpoint ¶
func NewDTLSEndpoint(ctx context.Context, transport net.Conn, asServer bool, certDER []byte, key *ecdsa.PrivateKey, profiles []SRTPProfile) (*DTLSEndpoint, error)
NewDTLSEndpoint wraps an underlying transport (any net.Conn — for our purposes a UDP socket already pre-set with the remote addr, or a packet-conn-backed pseudo-stream) and runs a DTLS handshake.
Profiles are the SRTP profiles to advertise in the use_srtp extension (RFC 5764 §4.1.1). Empty = our default order.
certDER + key identify our side; the peer commits to its own via the SDP fingerprint that the SIP layer validated separately against the cert presented in the handshake (RFC 5763 §3).
asServer = true when we're the DTLS server (a=setup:passive).
func (*DTLSEndpoint) AsClient ¶
func (e *DTLSEndpoint) AsClient() bool
AsClient reports whether this endpoint drove the handshake. Used by SRTPKeys.AsLocalRemote to swap the client/server material.
func (*DTLSEndpoint) Close ¶
func (e *DTLSEndpoint) Close() error
Close shuts the DTLS conn (which sends close_notify and stops reading). Idempotent.
func (*DTLSEndpoint) PeerCertificates ¶
func (e *DTLSEndpoint) PeerCertificates() [][]byte
PeerCertificates returns the DER-encoded certificates that the peer presented during the handshake. The leaf is index 0. Used by callers to verify against the SDP a=fingerprint per RFC 5763 §3 — without this check the cert is uncommitted and a passive MITM owns the call.
Returns nil before handshake completion or after Close.
func (*DTLSEndpoint) SRTPContexts ¶
func (e *DTLSEndpoint) SRTPContexts(keys *SRTPKeys) (rx, tx *srtp.Context, err error)
SRTPContexts builds inbound + outbound *srtp.Context from derived keying material, matched to this endpoint's role. Convenience for callers that want to enable SRTP on a Session immediately.
type DTLSResult ¶
type DTLSResult struct {
Endpoint *DTLSEndpoint
Keys *SRTPKeys
Err error
}
DTLSResult is what StartDTLS hands back when the handshake either completes successfully or fails. Either Endpoint+Keys are non-nil (success), or Err is non-nil (failure). Caller is responsible for closing the Endpoint when the call ends.
type RTCPStats ¶
type RTCPStats struct {
// PeerSeenRR tells callers whether our peer has emitted at least
// one RR about us; useful to distinguish "no RTCP support" from
// "no observed loss yet".
PeerSeenRR bool
// RTTMs is the round-trip time computed from the latest peer RR.
RTTMs uint32
// PeerJitter (in RTP clock units) as reported by peer's RR.
PeerJitter uint32
// PeerLossFraction is fraction-lost from peer's last RR (Q0.8).
PeerLossFraction uint8
// PeerCumulativeLost — peer's running total of dropped pkts of ours.
PeerCumulativeLost uint32
// LocalJitter is what we measured on the inbound stream.
LocalJitter uint32
// LocalPacketsRecv — RTP packets we accepted.
LocalPacketsRecv uint32
}
RTCPStats is the read-only snapshot exposed via SessionStats.
type RTPHeader ¶
type RTPHeader struct {
Version uint8
Padding bool
Extension bool
CSRCCount uint8
Marker bool
PayloadType uint8
SequenceNumber uint16
Timestamp uint32
SSRC uint32
}
RTPHeader RTP header fields (RFC 3550).
Notes: - This implementation supports:
- CSRC list (CC)
- header extension (X)
- padding (P)
- It does not attempt to interpret extension payloads beyond carrying raw bytes.
type RTPPacket ¶
type RTPPacket struct {
Header RTPHeader
// CSRC identifiers (0..15 entries).
CSRC []uint32
// If Header.Extension is true, these fields represent the extension header.
ExtensionProfile uint16
ExtensionPayload []byte // raw extension bytes
// Payload is the RTP payload after removing CSRC/extension/padding.
Payload []byte
}
RTPPacket RTP packet containing an RTP header and payload.
type SIPRTPTransport ¶
type SIPRTPTransport struct {
// PreserveSessionOnClose, if true, Close() does not close the underlying RTP UDP socket.
// Used when stopping the default MediaSession and handing media to an in-process bridge.
PreserveSessionOnClose bool
// OnInputPayload, if set, receives a copy of each incoming RTP payload before DTMF heuristics.
OnInputPayload func([]byte)
// OnInputRTP, if set, receives RTP timing metadata + payload copy for recording/reconstruction.
OnInputRTP func(seq uint16, ts uint32, payload []byte)
// OnOutputPayload, if set, receives a copy of each outgoing encoded audio RTP payload (output transport only).
OnOutputPayload func([]byte)
// OnOutputRTP, if set, receives RTP timing metadata + payload copy for recording/reconstruction.
// seq/ts correspond to the packet values before Session.SendRTP increments counters.
OnOutputRTP func(seq uint16, ts uint32, payload []byte)
// JitterPlaybackDelay, if > 0 on DirectionInput, delays playout and absorbs reorder/jitter (see DefaultJitterPlaybackDelay).
JitterPlaybackDelay time.Duration
// contains filtered or unexported fields
}
SIPRTPTransport adapts an RTP Session to the media.MediaTransport interface.
It is direction-aware:
- direction == media.DirectionInput : Next() reads from UDP & returns AudioPacket, Send() is a no-op
- direction == media.DirectionOutput: Send() writes AudioPacket as RTP, Next() is a no-op
func NewSIPRTPTransport ¶
func NewSIPRTPTransport(sess *Session, codec media.CodecConfig, direction string, telephoneEventPT uint8) *SIPRTPTransport
NewSIPRTPTransport creates a new SIPRTPTransport.
codec describes the negotiated RTP codec (from SDP), including sample rate, channels, bit depth and payload type. telephoneEventPT is the RTP payload type for telephone-event (RFC 2833); use 0 if not negotiated.
func (*SIPRTPTransport) Attach ¶
func (t *SIPRTPTransport) Attach(s *media.MediaSession)
Attach is called by MediaSession when the transport is registered.
func (*SIPRTPTransport) Close ¶
func (t *SIPRTPTransport) Close() error
Close closes the underlying RTP session.
func (*SIPRTPTransport) Codec ¶
func (t *SIPRTPTransport) Codec() media.CodecConfig
Codec returns the negotiated codec configuration.
func (*SIPRTPTransport) Next ¶
func (t *SIPRTPTransport) Next(ctx context.Context) (media.MediaPacket, error)
Next reads one RTP packet from the underlying Session and converts it to a media.AudioPacket for the input direction. For output transports it returns (nil, nil).
func (*SIPRTPTransport) Send ¶
func (t *SIPRTPTransport) Send(ctx context.Context, packet media.MediaPacket) (int, error)
Send sends a media.AudioPacket as a single RTP packet for the output direction. For input transports it is a no-op.
func (*SIPRTPTransport) String ¶
func (t *SIPRTPTransport) String() string
func (*SIPRTPTransport) WakeupRead ¶
func (t *SIPRTPTransport) WakeupRead()
WakeupRead unblocks a goroutine stuck in Next() (same idea as transfer RTP relay stop).
type SRTPKeys ¶
type SRTPKeys struct {
Profile SRTPProfile
ClientWriteKey []byte // 16 B
ClientWriteSalt []byte // 14 B
ServerWriteKey []byte // 16 B
ServerWriteSalt []byte // 14 B
}
SRTPKeys is the post-handshake material derived from the DTLS master secret per RFC 5764 §4.2. The "client" and "server" designations come from the handshake roles; downstream SRTP code uses them as "outbound from this side" / "inbound to this side" based on whether we drove the handshake (active = client).
func (*SRTPKeys) AsLocalRemote ¶
func (k *SRTPKeys) AsLocalRemote(iWasClient bool) (localKey, localSalt, remoteKey, remoteSalt []byte)
AsLocalRemote returns (localKey, localSalt, remoteKey, remoteSalt) based on whether this side drove the handshake.
- iWasClient = true → we are the client; clientWrite is OUR tx, serverWrite is the peer's tx (our rx)
- iWasClient = false → roles flipped
type SRTPProfile ¶
type SRTPProfile string
SRTPProfile names what we negotiated in the DTLS handshake. We only support the two RFC 5764 §4.1.2 must-implement profiles — adding more (AEAD_AES_128_GCM, AEAD_AES_256_GCM) is a follow-up once we hit a peer that requires them.
const ( // ProfileAES128CMHMACSHA180: AES_128_CM_HMAC_SHA1_80, 128-bit // confidentiality + 80-bit auth tag. The default everywhere. ProfileAES128CMHMACSHA180 SRTPProfile = "AES_CM_128_HMAC_SHA1_80" // ProfileAES128CMHMACSHA132: same cipher, 32-bit tag. Used by a // few legacy SBCs to save bandwidth on every packet. ProfileAES128CMHMACSHA132 SRTPProfile = "AES_CM_128_HMAC_SHA1_32" )
type Session ¶
type Session struct {
LocalAddr *net.UDPAddr
RemoteAddr *net.UDPAddr
Conn *net.UDPConn
// SSRC/sequence/timestamp are advanced by this session.
SSRC uint32
SeqNum uint16
Timestamp uint32
// contains filtered or unexported fields
}
Session is a minimal RTP-over-UDP session.
RTCP (and SRTCP) is not generated or parsed; SDP may still advertise a=rtcp for peer stacks. Optional SRTP (SDES) encrypts/decrypts RTP payloads only (Session.EnableSDESSRTP).
It is intentionally protocol-agnostic: - Timestamp increments are provided by the caller via `samples` argument. - Payload framing / codec packetization happens above this layer.
func NewSession ¶
NewSession creates a RTP UDP session.
If localPort is 0 or negative, the OS will choose an available ephemeral port.
func (*Session) AddMirrorRemote ¶
AddMirrorRemote adds a temporary extra RTP destination for outbound packets. Useful for NAT/ALG scenarios where real media port differs from SDP offer.
func (*Session) EnableDTLSSRTP ¶
EnableDTLSSRTP installs DTLS-derived SRTP contexts onto a Session. Mirrors EnableSDESSRTP but takes pre-built contexts to avoid duplicating the role-mapping logic between the two key sources.
func (*Session) EnableSDESSRTP ¶
func (s *Session) EnableSDESSRTP(peerOutboundKey, peerOutboundSalt, localOutboundKey, localOutboundSalt []byte) error
EnableSDESSRTP is a backwards-compatible shim that defaults to AES_CM_128_HMAC_SHA1_80. New callers should use EnableSDESSRTPWithProfile so they can carry the negotiated suite (RFC 3711 supports multiple auth tag lengths in addition to _80).
func (*Session) EnableSDESSRTPWithProfile ¶
func (s *Session) EnableSDESSRTPWithProfile(prof srtp.ProtectionProfile, peerOutboundKey, peerOutboundSalt, localOutboundKey, localOutboundSalt []byte) error
EnableSDESSRTPWithProfile configures SRTP using RFC 4568 SDES material with an explicit pion protection profile so callers that negotiated AES_CM_128_HMAC_SHA1_32 (Cisco / Avaya interop) don't silently fall back to _80 and end up with auth-tag length mismatches on the wire.
- rx context: decrypts the peer's outbound RTP (their tx → our rx)
- tx context: encrypts our outbound RTP (our tx → their rx)
Both contexts MUST use the same profile — RFC 4568 §6.1 forbids asymmetric suites within one m=audio block.
func (*Session) FirstPacket ¶
func (s *Session) FirstPacket() <-chan struct{}
FirstPacket returns a channel that is closed once the first RTP packet is received.
func (*Session) RTCPSnapshot ¶
RTCPSnapshot returns the RTCP-derived metrics. Nil-safe.
func (*Session) ReceiveRTP ¶
func (s *Session) ReceiveRTP(buffer []byte) (n int, from *net.UDPAddr, packet *RTPPacket, err error)
ReceiveRTP reads a UDP datagram and parses it into an RTPPacket.
It also opportunistically "learns" remote address (symmetric RTP behavior).
func (*Session) SendRTP ¶
SendRTP sends one RTP packet.
`samples` is the number of audio samples represented by `payload` at the RTP clock rate. For PCM-based codecs, this should match the negotiated codec frame duration.
func (*Session) SetRTCPClockRate ¶
SetRTCPClockRate sets the codec clock rate used for jitter computation. Default is 8000; call this with 48000 for Opus, or the actual negotiated rate otherwise.
func (*Session) SetRemoteAddr ¶
SetRemoteAddr sets the remote RTP address for outgoing packets.
func (*Session) StartDTLS ¶
func (s *Session) StartDTLS(ctx context.Context, asServer bool, certDER []byte, key *ecdsa.PrivateKey, profiles []SRTPProfile) <-chan DTLSResult
StartDTLS installs a DTLS demux route on this Session, then runs the handshake on a goroutine. The returned channel receives the result exactly once. Caller passes the cert/key it committed to in SDP (`a=fingerprint`); peer presents its own and the SDP layer is responsible for verifying the cert digest matches what was advertised (RFC 5763 §3) — that check is wired in slice A-4 alongside SDP offer/answer rendering.
asServer = true when our SDP `a=setup` resolved to "passive".
Concurrent StartDTLS calls on the same Session are an error; teardown happens via DTLSEndpoint.Close (the route is dropped from Session automatically).
func (*Session) StatsSnapshot ¶
func (s *Session) StatsSnapshot() SessionStats