Documentation
¶
Index ¶
- Constants
- Variables
- func AMRNBModeSet(fmtp string) []int
- func AMRNBOctetAligned(fmtp string) bool
- func AMRWBModeSet(fmtp string) []int
- func AMRWBOctetAligned(fmtp string) bool
- func AddressFamily(ip string) string
- func CanonicalizeAOR(u sip.Uri) string
- func ClampAMRNBMode(ceiling int, modeSet []int) int
- func ClampAMRWBMode(ceiling int, modeSet []int) int
- func DTMFDigitToEvent(digit rune) (uint8, bool)
- func DTMFEventToDigit(event uint8) (rune, bool)
- func EncodeDTMFEvent(ev DTMFEvent) []byte
- func FormatAMRNBModeSet(modes []int) string
- func FormatAMRWBModeSet(modes []int) string
- func FormatSessionExpires(interval uint32, refresher string) string
- func GenerateAnswer(cfg SDPConfig, selected codec.CodecType, selectedPT uint8, textRejected bool) []byte
- func GenerateDTMFPackets(digit rune, pt uint8, ssrc uint32, baseSeq uint16, baseTS uint32, ...) []*rtp.Packet
- func GenerateOffer(cfg SDPConfig) []byte
- func GenerateReInviteSDP(cfg SDPConfig, selected codec.CodecType, selectedPT uint8, direction string) []byte
- func IsWhatsAppInvite(call *InboundCall) bool
- func JoinHostPort(host string, port int) string
- func NegotiateCodec(remote *SDPMedia, supported []codec.CodecType) (codec.CodecType, uint8, bool)
- func NegotiateCodecPreferred(remote *SDPMedia, supported []codec.CodecType, preferred codec.CodecType) (codec.CodecType, uint8, bool)
- func ParseMinSE(value string) uint32
- func ParseSessionExpires(value string) (interval uint32, refresher string)
- func ParseSipfrag(body []byte) (int, string)
- func TelephoneEventClockRate(c codec.CodecType) int
- func UDPNetwork(listenIP string) string
- func WhatsAppRecipientURI(toUser string) sip.Uri
- type Binding
- type DTMFEvent
- type Engine
- func (e *Engine) AMRNBMode() int
- func (e *Engine) AMRNBOctetAligned() bool
- func (e *Engine) AMRWBMode() int
- func (e *Engine) AMRWBOctetAligned() bool
- func (e *Engine) AdvertisedIPForFamily(family string) string
- func (e *Engine) AllowHeader() sip.Header
- func (e *Engine) BindIP() string
- func (e *Engine) BindIPV6() string
- func (e *Engine) Codecs() []codec.CodecType
- func (e *Engine) ContactForInvite(req *sip.Request) *sip.ContactHeader
- func (e *Engine) DestinationsPinned() uint64
- func (e *Engine) DialogRespond(d *sipgo.DialogServerSession, statusCode int, reason string, body []byte, ...) error
- func (e *Engine) Invite(ctx context.Context, recipient sip.Uri, opts InviteOptions) (*OutboundCall, error)
- func (e *Engine) InviteWhatsApp(ctx context.Context, recipient sip.Uri, opts WhatsAppInviteOptions) (*WhatsAppOutboundCall, error)
- func (e *Engine) LogSyntheticResponse(req *sip.Request, statusCode int, reason string, body []byte, ...)
- func (e *Engine) OnInvite(handler func(*InboundCall))
- func (e *Engine) OnNotify(handler func(callID string, statusCode int, reason string, terminated bool))
- func (e *Engine) OnReInvite(handler func(callID string, direction string) []byte)
- func (e *Engine) OnRefer(...)
- func (e *Engine) OnUpdate(handler func(callID string, direction string, hasSDP bool) []byte)
- func (e *Engine) PortAllocator() *PortAllocator
- func (e *Engine) PublicHost() string
- func (e *Engine) Registrar() *Registrar
- func (e *Engine) RespondFromSource(tx sip.ServerTransaction, req *sip.Request, statusCode int, reason string) error
- func (e *Engine) RespondInviteSDP(dialog *sipgo.DialogServerSession, sdp []byte) error
- func (e *Engine) SIPDebug() bool
- func (e *Engine) SIPHost() string
- func (e *Engine) SendNotifySipfrag(ctx context.Context, dialog interface{}, statusCode int, reason string, ...) error
- func (e *Engine) SendReInvite(ctx context.Context, dialog interface{}, sdpBody []byte) error
- func (e *Engine) SendRefer(ctx context.Context, dialog interface{}, referTo string, ...) error
- func (e *Engine) Serve(ctx context.Context) error
- func (e *Engine) ServerHeader() sip.Header
- func (e *Engine) TLSPort() int
- func (e *Engine) Trunks() *TrunkManager
- func (e *Engine) UserAgentHeader() sip.Header
- type EngineConfig
- type ForkTarget
- type IPIPTrunkView
- type InboundCall
- type InviteOptions
- type OutboundCall
- type OutboundRegistration
- func (r *OutboundRegistration) AOR() string
- func (r *OutboundRegistration) AppID() string
- func (r *OutboundRegistration) Credentials() (string, string)
- func (r *OutboundRegistration) ID() string
- func (r *OutboundRegistration) PeerSocket() (host string, port int, transport string)
- func (r *OutboundRegistration) RegistrarURI() sip.Uri
- func (r *OutboundRegistration) Snapshot() TrunkView
- func (r *OutboundRegistration) Start(ctx context.Context)
- func (r *OutboundRegistration) Stop(ctx context.Context) error
- func (r *OutboundRegistration) Type() TrunkType
- type OutboundRegistrationConfig
- type OutboundRegistrationParams
- type PortAllocator
- type RTPSession
- func (s *RTPSession) Close() error
- func (s *RTPSession) LocalPort() int
- func (s *RTPSession) ReadRTP() (*rtp.Packet, error)
- func (s *RTPSession) SendKeepalive(payloadType uint8, count int)
- func (s *RTPSession) SetReadDeadline(t time.Time) error
- func (s *RTPSession) SetRemote(ip string, port int) error
- func (s *RTPSession) WriteRTP(pkt *rtp.Packet) error
- type Registrar
- func (r *Registrar) Bind(b Binding)
- func (r *Registrar) ClampExpires(requested int) int
- func (r *Registrar) Config() RegistrarConfig
- func (r *Registrar) List() []Binding
- func (r *Registrar) Lookup(aor string) (Binding, bool)
- func (r *Registrar) LookupAll(aor string) []Binding
- func (r *Registrar) Start(ctx context.Context)
- func (r *Registrar) UnbindAll(aor, reason string) int
- func (r *Registrar) UnbindContact(aor, contactURI, reason string) bool
- type RegistrarConfig
- type ReplacesParams
- type SDPConfig
- type SDPMedia
- type SDPTextMedia
- type SIPRegisterTrunkView
- type SessionTimerParams
- type Trunk
- type TrunkManager
- func (m *TrunkManager) Add(t Trunk)
- func (m *TrunkManager) Get(id string) Trunk
- func (m *TrunkManager) List() []Trunk
- func (m *TrunkManager) LookupByAORUser(user string) Trunk
- func (m *TrunkManager) LookupByFromAOR(aor string) Trunk
- func (m *TrunkManager) LookupByPeerSocket(host string, port int) Trunk
- func (m *TrunkManager) RefreshIndex(id string)
- func (m *TrunkManager) Remove(id string) Trunk
- func (m *TrunkManager) Shutdown(ctx context.Context)
- type TrunkStatus
- type TrunkType
- type TrunkView
- type WhatsAppInviteOptions
- type WhatsAppOutboundCall
Constants ¶
const ( // DefaultMinSE is the minimum session interval we accept (RFC 4028 §4). DefaultMinSE = 90 // DefaultSessionExpires is the default session interval when the remote // requests timers but doesn't specify an interval. DefaultSessionExpires = 1800 )
const (
WhatsAppOutboundHost = "wa.meta.vc"
)
Variables ¶
var ErrNotRTP = errors.New("not an RTP packet")
ErrNotRTP is returned by ReadRTP when a received UDP packet is not valid RTP (e.g. RTCP, STUN). Callers should continue reading on this error.
Functions ¶
func AMRNBModeSet ¶ added in v0.9.0
AMRNBModeSet parses an AMR-NB "mode-set=a,b,c" fmtp parameter into the set of allowed speech modes (0..7). Returns nil when no valid mode-set is present (RFC 4867: absence means all modes are permitted). Modes outside 0..7 are dropped.
func AMRNBOctetAligned ¶ added in v0.9.0
AMRNBOctetAligned reports whether the AMR-NB fmtp params select octet-aligned framing. Per RFC 4867 the default (no octet-align) is bandwidth-efficient, so an absent or "octet-align=0" parameter means bandwidth-efficient.
func AMRWBModeSet ¶ added in v0.9.0
AMRWBModeSet parses an AMR-WB "mode-set=a,b,c" fmtp parameter into the set of allowed speech modes (0..8). Returns nil when no valid mode-set is present (RFC 4867: absence means all modes are permitted). Modes outside 0..8 are dropped.
func AMRWBOctetAligned ¶ added in v0.9.0
AMRWBOctetAligned reports whether the AMR-WB fmtp params select octet-aligned framing. Per RFC 4867 the default (no octet-align) is bandwidth-efficient, so an absent or "octet-align=0" parameter means bandwidth-efficient.
func AddressFamily ¶ added in v0.6.0
AddressFamily returns the SDP address-type token ("IP4" or "IP6") for an IP literal, or "" when the input is empty / not a literal IP.
func CanonicalizeAOR ¶ added in v0.8.0
CanonicalizeAOR returns the registry key for an AOR URI. The transformation is: lower-case scheme, strip user-info parameters, lower-case host, keep port only when explicit, drop URI params. Returns "" for malformed input.
func ClampAMRNBMode ¶ added in v0.9.0
ClampAMRNBMode constrains a desired ceiling mode to the peer's negotiated mode-set. Behaviour mirrors ClampAMRWBMode but ranges over AMR-NB modes 0..7.
func ClampAMRWBMode ¶ added in v0.9.0
ClampAMRWBMode constrains a desired ceiling mode to the peer's negotiated mode-set: it returns the highest set member <= ceiling, or — when the ceiling is below every member — the lowest member (so the result always stays inside the set). A nil/empty set means no restriction, so the ceiling is returned.
func DTMFDigitToEvent ¶
DTMFDigitToEvent converts a DTMF digit character to its RFC 4733 event code.
func DTMFEventToDigit ¶
DTMFEventToDigit converts an RFC 4733 event code to a digit character.
func EncodeDTMFEvent ¶
EncodeDTMFEvent encodes a DTMFEvent into a 4-byte RFC 4733 payload.
func FormatAMRNBModeSet ¶ added in v0.9.0
FormatAMRNBModeSet renders modes as a "0,4,7" mode-set value (the inverse of AMRNBModeSet); returns "" for an empty set.
func FormatAMRWBModeSet ¶ added in v0.9.0
FormatAMRWBModeSet renders modes as a "0,1,2" mode-set value (the inverse of AMRWBModeSet); returns "" for an empty set.
func FormatSessionExpires ¶
FormatSessionExpires formats a Session-Expires header value.
func GenerateAnswer ¶
func GenerateAnswer(cfg SDPConfig, selected codec.CodecType, selectedPT uint8, textRejected bool) []byte
GenerateAnswer builds an SDP answer with a single selected codec. selectedPT echoes the remote offer's PT for dynamic codecs. When cfg.TextRTPPort != 0 the answer accepts RTT; when textRejected is true the answer includes a port=0 m=text section per RFC 3264.
func GenerateDTMFPackets ¶
func GenerateDTMFPackets(digit rune, pt uint8, ssrc uint32, baseSeq uint16, baseTS uint32, samplesPerPkt uint16) []*rtp.Packet
GenerateDTMFPackets builds the sequence of RTP packets for one DTMF digit. Returns 7 packets: 4 normal (80ms) + 3 end-of-event, each 20ms apart. The timestamp remains fixed for the entire event per RFC 4733. samplesPerPkt is the telephone-event clock rate per 20ms (e.g. 160 at 8kHz, 320 at AMR-WB's 16kHz); it sets the units of the encoded event duration and must match the negotiated telephone-event clock rate or strict peers drop the digit as too short.
func GenerateOffer ¶
GenerateOffer builds an SDP offer with all configured codecs.
func GenerateReInviteSDP ¶
func GenerateReInviteSDP(cfg SDPConfig, selected codec.CodecType, selectedPT uint8, direction string) []byte
GenerateReInviteSDP builds an SDP body for a re-INVITE (hold/unhold). It is similar to GenerateAnswer but uses the specified direction attribute.
func IsWhatsAppInvite ¶ added in v0.5.0
func IsWhatsAppInvite(call *InboundCall) bool
IsWhatsAppInvite reports whether an inbound INVITE comes from a Meta WhatsApp gateway (meta.vc or any subdomain).
func JoinHostPort ¶ added in v0.6.0
JoinHostPort wraps net.JoinHostPort with an int port — bracket-safe for IPv6 literals and a no-op for IPv4 / hostnames.
func NegotiateCodec ¶
NegotiateCodec finds the first codec in the remote SDP that is also in the supported list. Returns the codec type, the payload type from the remote SDP, and whether negotiation succeeded.
func NegotiateCodecPreferred ¶ added in v0.6.0
func NegotiateCodecPreferred(remote *SDPMedia, supported []codec.CodecType, preferred codec.CodecType) (codec.CodecType, uint8, bool)
NegotiateCodecPreferred is like NegotiateCodec but biases the choice toward preferred when it is non-zero. The preferred codec must appear in both the remote offer and the supported list; otherwise selection falls back to the regular preference order.
func ParseMinSE ¶
ParseMinSE parses a Min-SE header value, e.g. "90" → 90.
func ParseSessionExpires ¶
ParseSessionExpires parses a Session-Expires header value, e.g. "1800;refresher=uac" → interval=1800, refresher="uac".
func ParseSipfrag ¶ added in v0.3.0
ParseSipfrag returns (statusCode, reason) from a sipfrag status line, or (0,"").
func TelephoneEventClockRate ¶ added in v0.9.0
TelephoneEventClockRate returns the RTP clock rate to pair with the telephone-event (RFC 4733) format for codec c. RFC 4733 requires the telephone-event clock rate to match the audio codec's RTP clock rate, so AMR-WB uses 16 kHz; all other codecs use the conventional 8 kHz.
func UDPNetwork ¶ added in v0.6.0
UDPNetwork returns the UDP network string ("udp", "udp4", or "udp6") to use for a given listen IP literal. An empty string or "::" yields "udp" so the OS can give us a dual-stack socket on Linux when bindv6only=0.
func WhatsAppRecipientURI ¶ added in v0.5.0
WhatsAppRecipientURI builds the Request-URI for an outbound call. Meta uses "sip:+E164@wa.meta.vc;transport=tls" — using "sips:" returns 404 because their internal routing isn't strict-TLS end-to-end.
Types ¶
type Binding ¶ added in v0.8.0
type Binding struct {
AOR string
Contact string
Socket string
Transport string
UserAgent string
CallID string
AppID string
CreatedAt time.Time
LastRefresh time.Time
ExpiresAt time.Time
GrantedExpires int // seconds granted at bind time (constant for the binding)
}
Binding represents one Contact registered under an AOR.
type DTMFEvent ¶
type DTMFEvent struct {
Event uint8 // 0-9, 10=*, 11=#, 12-15=A-D
EndOfEvent bool
Volume uint8 // 0-63
Duration uint16 // in timestamp units
}
DTMFEvent represents an RFC 4733 telephone-event payload.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine wraps sipgo server/client + dialog caches for SIP signaling.
func NewEngine ¶
func NewEngine(cfg EngineConfig) (*Engine, error)
NewEngine creates a SIP engine with the given configuration.
func (*Engine) AMRNBMode ¶ added in v0.9.0
AMRNBMode returns the configured AMR-NB encoder speech mode (0..7).
func (*Engine) AMRNBOctetAligned ¶ added in v0.9.0
AMRNBOctetAligned reports whether AMR-NB offers advertise octet-aligned framing.
func (*Engine) AMRWBMode ¶ added in v0.9.0
AMRWBMode returns the configured AMR-WB encoder speech mode (0..8).
func (*Engine) AMRWBOctetAligned ¶ added in v0.9.0
AMRWBOctetAligned reports whether AMR-WB offers advertise octet-aligned framing.
func (*Engine) AdvertisedIPForFamily ¶ added in v0.6.0
AdvertisedIPForFamily returns the configured advertised IP for the given SDP address family token ("IP4" or "IP6"). Falls back to the other family when the requested one is unconfigured. Returns empty when neither is configured (caller should reject the call).
func (*Engine) AllowHeader ¶ added in v0.9.0
AllowHeader returns an Allow header listing every SIP method this UA answers (RFC 3261 §20.5). The list comes from sipgo's registered request handlers so it can never drift from the methods we actually accept.
func (*Engine) BindIP ¶
BindIP returns the engine's IPv4 advertised address. May be empty in IPv6-only deployments.
func (*Engine) BindIPV6 ¶ added in v0.6.0
BindIPV6 returns the engine's IPv6 advertised address. Empty when no v6 is configured.
func (*Engine) ContactForInvite ¶ added in v0.6.0
func (e *Engine) ContactForInvite(req *sip.Request) *sip.ContactHeader
ContactForInvite is the public form of contactForInvite, used by callers outside this package that need to attach a transport-appropriate Contact header to a dialog response.
func (*Engine) DestinationsPinned ¶ added in v0.6.0
DestinationsPinned returns the cumulative count of responses whose destination has been overridden via SIP_USE_SOURCE_SOCKET. Tests use this to assert the flag actually fired rather than silently no-op'd.
func (*Engine) DialogRespond ¶ added in v0.6.0
func (e *Engine) DialogRespond(d *sipgo.DialogServerSession, statusCode int, reason string, body []byte, headers ...sip.Header) error
DialogRespond sends a response on a UAS dialog, pinning the destination to the original INVITE source when SIP_USE_SOURCE_SOCKET is enabled. Replaces direct calls to dialog.Respond / dialog.RespondSDP — those reach tx.Respond internally without giving us a hook to override Destination.
func (*Engine) Invite ¶
func (e *Engine) Invite(ctx context.Context, recipient sip.Uri, opts InviteOptions) (*OutboundCall, error)
Invite sends an outbound INVITE and returns an OutboundCall on success. When opts.ForkTargets has more than one entry the INVITE is parallel-forked (RFC 3261 §16); the first branch to reach a 2xx response wins and the others are CANCELled.
func (*Engine) InviteWhatsApp ¶ added in v0.5.0
func (e *Engine) InviteWhatsApp(ctx context.Context, recipient sip.Uri, opts WhatsAppInviteOptions) (*WhatsAppOutboundCall, error)
InviteWhatsApp sends an outbound INVITE over SIP/TLS with a pre-built WebRTC SDP offer and digest auth credentials.
func (*Engine) LogSyntheticResponse ¶ added in v0.5.0
func (e *Engine) LogSyntheticResponse(req *sip.Request, statusCode int, reason string, body []byte, headers ...sip.Header)
LogSyntheticResponse constructs a response from a request (mirroring what sipgo would build internally) purely for SIP_DEBUG logging. The actual response still goes out through dialog.Respond / dialog.RespondSDP; this is a best-effort wire-form dump so the body and headers we ask sipgo to include are visible on the debug channel.
func (*Engine) OnInvite ¶
func (e *Engine) OnInvite(handler func(*InboundCall))
OnInvite registers a handler for inbound INVITE requests.
func (*Engine) OnNotify ¶ added in v0.3.0
func (e *Engine) OnNotify(handler func(callID string, statusCode int, reason string, terminated bool))
OnNotify registers a handler for in-dialog NOTIFY requests carrying a "refer" subscription (RFC 3515 sipfrag). It is invoked once per NOTIFY with the subscription's terminal/transient SIP status parsed from the sipfrag body.
func (*Engine) OnReInvite ¶
OnReInvite registers a handler for in-dialog re-INVITE requests (hold/unhold). The handler receives the SIP Call-ID and the SDP direction attribute, and returns the SDP body to include in the 200 OK response (nil = no SDP).
func (*Engine) OnRefer ¶ added in v0.3.0
func (e *Engine) OnRefer(handler func(callID string, target string, replaces *ReplacesParams, req *sip.Request, tx sip.ServerTransaction))
OnRefer registers a handler for in-dialog REFER requests (transfer). The handler is responsible for sending the SIP response (typically 202 Accepted, or 603 Decline when transfers are disabled). req is provided so the handler can pass it to sip.NewResponseFromRequest.
func (*Engine) OnUpdate ¶ added in v0.9.0
OnUpdate registers a handler for in-dialog UPDATE requests (RFC 3311), used for session-timer refresh (RFC 4028) and mid-dialog media changes. hasSDP indicates whether the UPDATE carried an SDP offer; when true, direction is the parsed a=sendrecv/sendonly/recvonly/inactive attribute and the returned []byte is the SDP answer for the 200 OK. When false, direction is "" and the handler should only refresh session-timer state.
func (*Engine) PortAllocator ¶
func (e *Engine) PortAllocator() *PortAllocator
PortAllocator returns the engine's port allocator (nil if OS-assigned).
func (*Engine) PublicHost ¶ added in v0.5.0
PublicHost returns the canonical signalling hostname (SIP_DOMAIN when set, otherwise the advertised IP).
func (*Engine) Registrar ¶ added in v0.8.0
Registrar returns the engine's AOR registry (may be nil when the engine was created without one).
func (*Engine) RespondFromSource ¶ added in v0.3.0
func (e *Engine) RespondFromSource(tx sip.ServerTransaction, req *sip.Request, statusCode int, reason string) error
RespondFromSource pins the response destination to the request's UDP source so peers with unroutable Via headers still get our reply.
func (*Engine) RespondInviteSDP ¶ added in v0.5.0
func (e *Engine) RespondInviteSDP(dialog *sipgo.DialogServerSession, sdp []byte) error
RespondInviteSDP sends a 2xx response to an inbound INVITE with a transport-appropriate Contact header. This is required for WhatsApp inbound calls, which arrive over TLS and need a sips: Contact pointing at our TLS port — otherwise the remote's ACK is routed to the wrong scheme/port, the dialog stays in Early state, and retransmits eventually kill the transaction.
func (*Engine) SIPDebug ¶ added in v0.5.0
SIPDebug reports whether SIP_DEBUG is enabled. Consumers that send responses via sipgo (dialog.Respond / RespondSDP) use this to gate LogSyntheticResponse calls.
func (*Engine) SendNotifySipfrag ¶ added in v0.3.0
func (e *Engine) SendNotifySipfrag(ctx context.Context, dialog interface{}, statusCode int, reason string, terminated bool) error
SendNotifySipfrag sends a "refer" subscription NOTIFY with a sipfrag body.
func (*Engine) SendReInvite ¶
SendReInvite sends a re-INVITE within an existing dialog for hold/unhold. dialog must be either *sipgo.DialogServerSession or *sipgo.DialogClientSession.
func (*Engine) SendRefer ¶ added in v0.3.0
func (e *Engine) SendRefer(ctx context.Context, dialog interface{}, referTo string, replaces *ReplacesParams) error
SendRefer sends an in-dialog REFER, returning nil on 202 Accepted.
func (*Engine) Serve ¶
Serve starts the SIP server and blocks until ctx is cancelled. When TLSBindPort is configured it runs UDP and TLS listeners concurrently; if either fails the other is torn down via ctx cancellation. A secondary UDP listener is started when listenIPV6 is set to a literal distinct from listenIP (rare dual-bind case for hosts with bindv6only=1).
func (*Engine) ServerHeader ¶ added in v0.4.0
ServerHeader returns a SIP Server header for UAS responses.
func (*Engine) TLSPort ¶ added in v0.5.0
TLSPort returns the configured SIP TLS port (0 = disabled).
func (*Engine) Trunks ¶ added in v0.9.0
func (e *Engine) Trunks() *TrunkManager
Trunks returns the engine's outbound SIP trunk manager. Always non-nil.
func (*Engine) UserAgentHeader ¶ added in v0.9.0
UserAgentHeader returns a SIP User-Agent header for UAC requests. Mirrors the Server header so peers see the same identity in both directions.
type EngineConfig ¶
type EngineConfig struct {
BindIP string // IPv4 advertised address for SDP c= line / Contact (when v4 is in use)
BindIPV6 string // IPv6 advertised address; empty = v6 not advertised
ListenIP string // IPv4 socket bind (default: same as BindIP). Special values: "0.0.0.0", "::" (dual-stack)
ListenIPV6 string // IPv6 socket bind (default: same as BindIPV6). Used when configured separately from ListenIP.
ExternalIP string // IPv4 public IP override for NAT/Docker (v6 has no equivalent — set BindIPV6 directly)
PublicHost string // FQDN advertised in From/Contact/Via signaling headers; falls back to ExternalIP/BindIP when empty
BindPort int
TLSBindPort int // 0 = TLS disabled
TLSCertPath string // CA-signed cert (fullchain.pem) — required when TLSBindPort > 0
TLSKeyPath string // private key (privkey.pem) — required when TLSBindPort > 0
SIPDebug bool // dump full SIP request/response bodies on the debug channel
SIPHost string
// UseSourceSocket forces SIP responses and in-dialog requests to be
// routed back to the request's source socket (req.Source()) instead of
// the peer's Contact URI / Via sent-by. Required when peers are behind
// NAT and advertise unroutable addresses.
UseSourceSocket bool
Codecs []codec.CodecType
// AMRWBMode is the AMR-WB encoder speech mode (0..8) offered/transmitted.
AMRWBMode int
// AMRWBOctetAligned selects octet-aligned (true) vs bandwidth-efficient
// (false) AMR-WB framing in generated offers.
AMRWBOctetAligned bool
// AMRNBMode is the AMR-NB encoder speech mode (0..7) offered/transmitted.
AMRNBMode int
// AMRNBOctetAligned selects octet-aligned (true) vs bandwidth-efficient
// (false) AMR-NB framing in generated offers.
AMRNBOctetAligned bool
Log *slog.Logger
PortAllocator *PortAllocator // nil = OS-assigned ports
// Registrar, when non-nil, enables inbound REGISTER handling and AOR
// resolution for outbound INVITEs.
Registrar *Registrar
}
EngineConfig holds configuration for the SIP engine.
type ForkTarget ¶ added in v0.8.0
ForkTarget identifies one branch of a parallel-forked INVITE: an explicit transport-layer destination (ip:port) that overrides the routing implied by the Request-URI's host. Used when dialing a registered AOR with one or more bound contacts; one ForkTarget per bound contact.
type IPIPTrunkView ¶ added in v0.9.0
type IPIPTrunkView struct {
PeerURI string `json:"peer_uri,omitempty"`
}
IPIPTrunkView is the placeholder shape for the unimplemented ip_ip type.
type InboundCall ¶
type InboundCall struct {
Dialog *sipgo.DialogServerSession
From string // caller URI user part
To string // callee URI user part
RemoteSDP *SDPMedia // parsed offer SDP
Request *sip.Request
// Session timer (RFC 4028) — populated when remote requests timers.
SessionTimer *SessionTimerParams // nil when remote didn't request timers
}
InboundCall wraps a sipgo DialogServerSession with parsed SDP.
type InviteOptions ¶
type InviteOptions struct {
Codecs []codec.CodecType // Override engine codecs for this call; nil = use engine default
Headers []sip.Header // Extra SIP headers to include in the INVITE
FromUser string // Override the user part of the From header (caller ID)
OnEarlyMedia func(remoteSDP *SDPMedia, rtpSess *RTPSession) // Called on first 183 with SDP
AuthUsername string // SIP digest auth username (optional)
AuthPassword string // SIP digest auth password (optional)
// ForkTargets, when non-empty, routes the INVITE to the listed transport
// addresses. A single target overrides the destination for a single
// outbound dialog; multiple targets parallel-fork the INVITE and race
// for the first 2xx (RFC 3261 §16).
ForkTargets []ForkTarget
// RouteURI, when set, adds a single loose-route header pointing at the
// given URI (typically a trunk's upstream proxy/registrar). The ";lr"
// param is appended if not already present.
RouteURI *sip.Uri
// RTT (T.140 / RFC 4103) parameters. RTTEnabled offers m=text alongside
// audio in the INVITE. RTTRedundancy controls the RFC 2198 RED depth
// (0 = plain T.140, no RED).
RTTEnabled bool
RTTRedundancy int
}
InviteOptions holds optional parameters for outbound INVITE.
type OutboundCall ¶
type OutboundCall struct {
Dialog *sipgo.DialogClientSession
RemoteSDP *SDPMedia
RTPSess *RTPSession
// Optional RTT (T.140 / RFC 4103) media. Populated when the remote's
// answer accepts the offered m=text section. Nil otherwise.
TextRTPSess *RTPSession
// Session timer (RFC 4028) — populated when remote's 200 OK includes timers.
SessionTimer *SessionTimerParams // nil when remote didn't include timers
}
OutboundCall wraps a sipgo DialogClientSession with parsed answer SDP.
type OutboundRegistration ¶ added in v0.9.0
type OutboundRegistration struct {
// contains filtered or unexported fields
}
OutboundRegistration is the sip_register Trunk implementation. One instance per trunk; safe for concurrent access.
func NewOutboundRegistration ¶ added in v0.9.0
func NewOutboundRegistration(engine *Engine, bus *events.Bus, log *slog.Logger, cfg OutboundRegistrationConfig, p OutboundRegistrationParams) *OutboundRegistration
NewOutboundRegistration constructs a trunk in the registering state. Call Start to launch the background lifecycle.
func (*OutboundRegistration) AOR ¶ added in v0.9.0
func (r *OutboundRegistration) AOR() string
func (*OutboundRegistration) AppID ¶ added in v0.9.0
func (r *OutboundRegistration) AppID() string
func (*OutboundRegistration) Credentials ¶ added in v0.9.0
func (r *OutboundRegistration) Credentials() (string, string)
Credentials returns the trunk's digest username/password. Used by the outbound INVITE path; never exposed over the API.
func (*OutboundRegistration) ID ¶ added in v0.9.0
func (r *OutboundRegistration) ID() string
func (*OutboundRegistration) PeerSocket ¶ added in v0.9.0
func (r *OutboundRegistration) PeerSocket() (host string, port int, transport string)
func (*OutboundRegistration) RegistrarURI ¶ added in v0.9.0
func (r *OutboundRegistration) RegistrarURI() sip.Uri
RegistrarURI exposes the configured upstream registrar URI; used by the outbound INVITE path to attach a Route header.
func (*OutboundRegistration) Snapshot ¶ added in v0.9.0
func (r *OutboundRegistration) Snapshot() TrunkView
Snapshot returns the current TrunkView. Safe to call concurrently.
func (*OutboundRegistration) Start ¶ added in v0.9.0
func (r *OutboundRegistration) Start(ctx context.Context)
Start launches the background register-and-refresh loop. Calling Start more than once is a no-op.
func (*OutboundRegistration) Stop ¶ added in v0.9.0
func (r *OutboundRegistration) Stop(ctx context.Context) error
Stop cancels the refresh loop and sends a final REGISTER with Expires: 0. Best-effort; honours ctx.
func (*OutboundRegistration) Type ¶ added in v0.9.0
func (r *OutboundRegistration) Type() TrunkType
type OutboundRegistrationConfig ¶ added in v0.9.0
type OutboundRegistrationConfig struct {
DefaultExpiresSeconds int
MinExpiresSeconds int
MaxExpiresSeconds int
RefreshRatio float64
FailureBackoffMax time.Duration
}
OutboundRegistrationConfig holds tunables shared across every sip_register trunk.
type OutboundRegistrationParams ¶ added in v0.9.0
type OutboundRegistrationParams struct {
ID string
AppID string
RegistrarURI sip.Uri
AOR sip.Uri
Username string
Password string
ContactUser string
RequestedExpiresSeconds int
}
OutboundRegistrationParams is the per-trunk creation payload (the values that survive validation of a CreateTrunkRequest of type sip_register).
type PortAllocator ¶
type PortAllocator struct {
// contains filtered or unexported fields
}
PortAllocator manages a pool of UDP ports within a configured range. It is safe for concurrent use.
func NewPortAllocator ¶
func NewPortAllocator(min, max int) (*PortAllocator, error)
NewPortAllocator creates a port allocator for the given range [min, max]. Returns nil if min and max are both 0 (use OS-assigned ports).
func (*PortAllocator) Allocate ¶
func (pa *PortAllocator) Allocate() (int, error)
Allocate returns the next available port from the pool.
func (*PortAllocator) Range ¶
func (pa *PortAllocator) Range() (int, int)
Range returns the configured min and max ports.
func (*PortAllocator) Release ¶
func (pa *PortAllocator) Release(port int)
Release returns a port back to the pool.
type RTPSession ¶
type RTPSession struct {
// contains filtered or unexported fields
}
RTPSession manages a UDP socket for RTP send/receive. It implements symmetric RTP (RFC 4961): once an RTP packet is received, the remote address is latched to the source IP:port of that packet, overriding the SDP-provided address. This is essential for NAT traversal.
func NewRTPSession ¶
func NewRTPSession() (*RTPSession, error)
NewRTPSession creates a new RTP session listening on a random UDP port. The socket is dual-stack on Linux (accepts both v4 and v6 when /proc/sys/net/ipv6/bindv6only is 0, which is the default).
func NewRTPSessionFromAllocator ¶
func NewRTPSessionFromAllocator(alloc *PortAllocator) (*RTPSession, error)
NewRTPSessionFromAllocator creates an RTP session using a port from the allocator's pool. If alloc is nil, behaves like NewRTPSession (OS-assigned).
func NewRTPSessionOnPort ¶
func NewRTPSessionOnPort(port int) (*RTPSession, error)
NewRTPSessionOnPort creates a new RTP session on a specific local port. Same dual-stack semantics as NewRTPSession.
func (*RTPSession) Close ¶
func (s *RTPSession) Close() error
Close closes the UDP connection and releases the port back to the allocator. Idempotent: subsequent calls are no-ops. Multiple rollback paths in the engine ladder can each call Close on the same session.
func (*RTPSession) LocalPort ¶
func (s *RTPSession) LocalPort() int
LocalPort returns the local UDP port this session is listening on.
func (*RTPSession) ReadRTP ¶
func (s *RTPSession) ReadRTP() (*rtp.Packet, error)
ReadRTP reads and unmarshals an RTP packet from the UDP socket. Blocks until data arrives. Implements symmetric RTP: the remote address is latched to the source IP:port of each incoming RTP packet.
func (*RTPSession) SendKeepalive ¶
func (s *RTPSession) SendKeepalive(payloadType uint8, count int)
SendKeepalive sends a small burst of silence RTP packets to the remote address. This is used immediately after SetRemote on outbound calls to punch through NAT devices (port-latching) before the leg's full media pipeline starts.
func (*RTPSession) SetReadDeadline ¶
func (s *RTPSession) SetReadDeadline(t time.Time) error
SetReadDeadline sets a deadline on the underlying UDP socket for reads.
type Registrar ¶ added in v0.8.0
type Registrar struct {
// contains filtered or unexported fields
}
Registrar is the in-memory AOR registry. Safe for concurrent use.
func NewRegistrar ¶ added in v0.8.0
NewRegistrar constructs a registrar. bus may be nil (events are dropped).
func (*Registrar) Bind ¶ added in v0.8.0
Bind inserts a new binding or refreshes an existing one (same AOR + Contact identity). When AllowMultipleContacts is false, any pre-existing bindings for the AOR are removed first (one event each, reason "replaced"). Emits sip.registration_active.
func (*Registrar) ClampExpires ¶ added in v0.8.0
ClampExpires clamps a requested expiry to [60, MaxExpiresSeconds]. A non-positive input is treated as the default.
func (*Registrar) Config ¶ added in v0.8.0
func (r *Registrar) Config() RegistrarConfig
Config returns the effective configuration (with defaults applied).
func (*Registrar) List ¶ added in v0.8.0
List returns every binding across all AORs. Order is not specified.
func (*Registrar) Lookup ¶ added in v0.8.0
Lookup returns the most-recently-refreshed binding for an AOR. The second return value is false when no binding exists.
func (*Registrar) LookupAll ¶ added in v0.8.0
LookupAll returns every binding for an AOR, sorted most-recently-refreshed first.
func (*Registrar) Start ¶ added in v0.8.0
Start launches the expiry sweeper. Returns immediately; the sweeper runs until ctx is cancelled.
func (*Registrar) UnbindAll ¶ added in v0.8.0
UnbindAll removes every binding under the AOR. One event per binding. Returns the number of removed bindings.
func (*Registrar) UnbindContact ¶ added in v0.8.0
UnbindContact removes a single contact under an AOR. Returns true if a binding was removed.
type RegistrarConfig ¶ added in v0.8.0
type RegistrarConfig struct {
DefaultExpiresSeconds int
MaxExpiresSeconds int
SweepInterval time.Duration
AllowMultipleContacts bool
}
RegistrarConfig holds tunables for the registrar.
type ReplacesParams ¶ added in v0.3.0
ReplacesParams carries the Replaces header dialog identity (RFC 3891).
func ParseReferTo ¶ added in v0.3.0
func ParseReferTo(value string) (string, *ReplacesParams, error)
ParseReferTo extracts the bare URI and (optional) Replaces from a Refer-To.
func (*ReplacesParams) String ¶ added in v0.3.0
func (p *ReplacesParams) String() string
String formats as the Replaces value embedded in a Refer-To URI.
type SDPConfig ¶
type SDPConfig struct {
LocalIP string
RTPPort int
Codecs []codec.CodecType // Offered/supported codecs in preference order
// Optional RTT (T.140 / RFC 4103) section. Set TextRTPPort != 0 to emit
// an m=text line in offers/answers/re-INVITEs. TextT140PT and TextREDPT
// are dynamic payload types; TextREDPT == 0 disables RFC 2198 redundancy.
// RTTRedundancy is the number of t140/t140/.../t140 generations declared
// in the RED fmtp.
TextRTPPort int
TextT140PT uint8
TextREDPT uint8
RTTRedundancy int
// AMRWBOctetAligned controls the AMR-WB fmtp emitted for an offer/answer:
// true emits "octet-align=1", false emits no octet-align (RFC 4867 default,
// bandwidth-efficient). On answers it must echo the peer's negotiated format.
AMRWBOctetAligned bool
// AMRWBModeSet, when non-empty (e.g. "0,1,2"), adds a "mode-set=..." AMR-WB
// fmtp param. Used on answers to echo the peer's negotiated mode-set per
// RFC 4867; left empty on offers (we accept all modes on receive).
AMRWBModeSet string
// AMRNBOctetAligned controls the AMR-NB fmtp emitted for an offer/answer:
// true emits "octet-align=1", false emits no octet-align (RFC 4867 default,
// bandwidth-efficient). On answers it must echo the peer's negotiated format.
AMRNBOctetAligned bool
// AMRNBModeSet, when non-empty (e.g. "0,4,7"), adds a "mode-set=..." AMR-NB
// fmtp param. Used on answers to echo the peer's negotiated mode-set per
// RFC 4867; left empty on offers (we accept all modes on receive).
AMRNBModeSet string
// DTMFPT, when non-zero, is the telephone-event (RFC 4733) PT to advertise
// in the generated SDP. Zero defaults to 101. In answers callers should set
// this from the remote offer to mirror the offerer's choice.
DTMFPT uint8
// DTMFClockRate, when non-zero, is the telephone-event clock rate (Hz) to
// advertise. Zero defaults to TelephoneEventClockRate(selected codec). In
// answers callers must set this from the remote offer so RFC 3264
// offer/answer semantics hold — phones like Fanvil pin telephone-event at
// 8 kHz regardless of audio codec, and unilaterally upgrading to 16 kHz
// breaks their DTMF.
DTMFClockRate int
}
SDPConfig holds local media parameters for SDP generation.
type SDPMedia ¶
type SDPMedia struct {
RemoteIP string
RemotePort int
AddressFamily string // "IP4" or "IP6" (from c= line); empty if not present
Codecs []codec.CodecType // Codecs from m= line, in offer order
CodecPTs map[codec.CodecType]uint8 // Actual PT for each codec from remote SDP
CodecRates map[codec.CodecType]int // Clock rate (Hz) for each codec, from a=rtpmap; falls back to codec default
CodecFmtp map[codec.CodecType]string // Raw a=fmtp params for each codec (e.g. AMR-WB "octet-align=1; mode-set=...")
Ptime int // ms, default 20
Direction string // "sendrecv", "sendonly", "recvonly", "inactive"; empty = sendrecv
DTMFEventPTs map[uint8]int // telephone-event (RFC 4733) PT -> clock rate, as advertised by the remote
// Text (RTT, T.140 / RFC 4103). Non-nil when the remote SDP carried an
// m=text line with a non-zero port. A port of zero (peer rejecting the
// text section per RFC 3264) leaves this field nil.
Text *SDPTextMedia
}
SDPMedia holds parsed remote media parameters.
func (*SDPMedia) DTMFPTForRate ¶ added in v0.9.0
DTMFPTForRate returns the remote telephone-event payload type advertised at the given clock rate, if any.
func (*SDPMedia) PreferredDTMFEvent ¶ added in v0.9.0
PreferredDTMFEvent returns the telephone-event PT and clock rate to mirror in an answer (RFC 3264 offer/answer). When the remote advertised multiple telephone-event lines, the lowest PT wins for determinism. Returns (0, 0, false) when no telephone-event was offered.
type SDPTextMedia ¶ added in v0.6.0
type SDPTextMedia struct {
RemoteIP string
RemotePort int
T140PT uint8 // 0 if no t140/1000 advertised
REDPT uint8 // 0 if no red/1000 advertised
Direction string
}
SDPTextMedia holds parsed remote RTT parameters.
type SIPRegisterTrunkView ¶ added in v0.9.0
type SIPRegisterTrunkView struct {
RegistrarURI string `json:"registrar_uri"`
AOR string `json:"aor"`
Username string `json:"username,omitempty"`
ContactURI string `json:"contact_uri,omitempty"`
RequestedExpiresSeconds int `json:"requested_expires_seconds"`
GrantedExpiresSeconds int `json:"granted_expires_seconds,omitempty"`
LastRegisteredAt string `json:"last_registered_at,omitempty"`
NextRefreshAt string `json:"next_refresh_at,omitempty"`
CallID string `json:"call_id,omitempty"`
CSeq uint32 `json:"cseq,omitempty"`
// SourceAddress is the host:port the registrar's most recent response
// actually came from. Initially set from the configured RegistrarURI;
// updated to the real transport address (which may differ from the URI
// when DNS / load-balancing fronts the registrar) on each 2xx response.
// Used as the key for tagging inbound INVITEs back to this trunk.
SourceAddress string `json:"source_address,omitempty"`
}
SIPRegisterTrunkView holds the sip_register-specific runtime fields. Credentials (password) are never exposed.
type SessionTimerParams ¶
type SessionTimerParams struct {
Interval uint32 // Session-Expires delta-seconds
Refresher string // "uac" or "uas"
MinSE uint32 // Min-SE value (0 = not present)
}
SessionTimerParams holds parsed Session-Expires / Min-SE values from a SIP request or response.
type Trunk ¶ added in v0.9.0
type Trunk interface {
ID() string
Type() TrunkType
// AOR returns the canonical identity used to match outbound POST /v1/legs
// `from` against this trunk. Empty when the trunk type has no AOR concept.
AOR() string
// PeerSocket returns the upstream peer's transport address used to tag
// inbound INVITEs delivered by this trunk. Returns empty host with port 0
// when not yet known.
PeerSocket() (host string, port int, transport string)
AppID() string
Snapshot() TrunkView
// Start launches the background lifecycle (REGISTER + refresh for
// sip_register). Returns immediately.
Start(ctx context.Context)
// Stop tears the trunk down (de-register for sip_register). Best-effort;
// honours ctx for timeout.
Stop(ctx context.Context) error
}
Trunk is the abstract resource managed by the TrunkManager. Each concrete type (sip_register, future ip_ip, ...) implements this interface; lookups over the manager are type-agnostic.
type TrunkManager ¶ added in v0.9.0
type TrunkManager struct {
// contains filtered or unexported fields
}
TrunkManager is the concurrent registry of all SIP trunks (sip_register today; ip_ip etc. in the future). Lookups are type-agnostic and indexed by id, canonical AOR, and upstream peer socket.
func NewTrunkManager ¶ added in v0.9.0
func NewTrunkManager() *TrunkManager
func (*TrunkManager) Add ¶ added in v0.9.0
func (m *TrunkManager) Add(t Trunk)
Add inserts a trunk, indexing it by id, AOR (when non-empty), and peer socket (when known).
func (*TrunkManager) Get ¶ added in v0.9.0
func (m *TrunkManager) Get(id string) Trunk
Get returns the trunk with the given id, or nil.
func (*TrunkManager) List ¶ added in v0.9.0
func (m *TrunkManager) List() []Trunk
List returns every registered trunk in indeterminate order.
func (*TrunkManager) LookupByAORUser ¶ added in v0.9.0
func (m *TrunkManager) LookupByAORUser(user string) Trunk
LookupByAORUser returns a trunk whose AOR user-part matches the given user string. Falls back when the caller only supplied a bare user on `from` (e.g. POST /v1/legs with `from: "alice"`) — the engine's publicHost won't match the trunk's upstream domain. Returns nil if no match; if multiple trunks have the same user, returns one arbitrarily.
func (*TrunkManager) LookupByFromAOR ¶ added in v0.9.0
func (m *TrunkManager) LookupByFromAOR(aor string) Trunk
LookupByFromAOR returns the trunk whose canonical AOR matches the given URI (after canonicalisation), or nil. The input may be a raw SIP URI string ("sip:alice@host") or already-canonical form.
func (*TrunkManager) LookupByPeerSocket ¶ added in v0.9.0
func (m *TrunkManager) LookupByPeerSocket(host string, port int) Trunk
LookupByPeerSocket returns the trunk whose upstream peer matches the given transport address, or nil. Matches first on full host:port, then on host alone (for cases where the peer's ephemeral source port differs from the configured registrar port).
func (*TrunkManager) RefreshIndex ¶ added in v0.9.0
func (m *TrunkManager) RefreshIndex(id string)
RefreshIndex re-indexes the trunk under its current AOR and peer socket. Call this after a trunk's PeerSocket() value becomes known (e.g. after the first successful REGISTER reveals the registrar's actual transport addr).
func (*TrunkManager) Remove ¶ added in v0.9.0
func (m *TrunkManager) Remove(id string) Trunk
Remove removes a trunk by id (does not Stop it). Returns the removed trunk, or nil if absent.
func (*TrunkManager) Shutdown ¶ added in v0.9.0
func (m *TrunkManager) Shutdown(ctx context.Context)
Shutdown stops every trunk in parallel, honouring ctx. After return the manager is empty.
type TrunkStatus ¶ added in v0.9.0
type TrunkStatus string
TrunkStatus is the runtime state of a trunk's upstream connection.
const ( TrunkStatusRegistering TrunkStatus = "registering" TrunkStatusActive TrunkStatus = "active" TrunkStatusFailed TrunkStatus = "failed" TrunkStatusUnregistering TrunkStatus = "unregistering" TrunkStatusExpired TrunkStatus = "expired" )
type TrunkType ¶ added in v0.9.0
type TrunkType string
TrunkType discriminates the upstream connection style of a SIP trunk. Only TrunkTypeSIPRegister is implemented today; other values are reserved for the OpenAPI/AsyncAPI contract and rejected at request time.
const ( // TrunkTypeSIPRegister: VoiceBlender acts as a UAC and REGISTERs to an // upstream registrar, refreshing periodically; calls in either direction // flow through that registered identity. TrunkTypeSIPRegister TrunkType = "sip_register" // TrunkTypeIPIP: reserved for static-IP peering (no REGISTER). Not yet // implemented; the trunks handler returns 501 when requested. TrunkTypeIPIP TrunkType = "ip_ip" )
type TrunkView ¶ added in v0.9.0
type TrunkView struct {
ID string `json:"id"`
Type TrunkType `json:"type"`
AppID string `json:"app_id,omitempty"`
Status TrunkStatus `json:"status"`
LastError string `json:"last_error,omitempty"`
CreatedAt string `json:"created_at"`
SIPRegister *SIPRegisterTrunkView `json:"sip_register,omitempty"`
IPIP *IPIPTrunkView `json:"ip_ip,omitempty"`
}
TrunkView is the JSON-friendly snapshot of a trunk's current state. Each type populates its own sub-struct; only one of `SIPRegister` / `IPIP` is non-nil per snapshot.
type WhatsAppInviteOptions ¶ added in v0.5.0
type WhatsAppInviteOptions struct {
// FromNumber is the business phone number in E.164. Used as the From
// URI user (with leading '+'). When DigestUsername is empty it also
// serves as the digest auth username (with the '+' stripped, per Meta).
FromNumber string
DigestUsername string // optional override; defaults to FromNumber without '+'
Password string
SDPOffer []byte
Headers []sip.Header
}
type WhatsAppOutboundCall ¶ added in v0.5.0
type WhatsAppOutboundCall struct {
Dialog *sipgo.DialogClientSession
AnswerSDP []byte
}