outbound

package
v1.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 9, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package outbound implements SIP UAC (outbound) signaling without media binding.

It shares the UDP socket with inbound UAS via SignalingSender (see gateway.Endpoint). Responses are routed through Manager.HandleSIPResponse on stack.EndpointConfig.OnSIPResponse.

Supported signaling:

  • INVITE / provisional / 200 OK + ACK
  • CANCEL (RFC 3261 §9.1, with retransmit)
  • BYE (in-dialog)
  • UPDATE session-timer refresh (RFC 4028 UAC refresher)
  • UDP / TCP / TLS transport selection and connection pooling

Index

Constants

View Source
const (
	DialEventInvited     = "invited"
	DialEventProvisional = "provisional"
	DialEventEstablished = "established"
	DialEventFailed      = "failed"
)

Variables

View Source
var (
	// ErrNoSignalingSender is returned when Dial is called before BindSender.
	ErrNoSignalingSender = errors.New("sip/outbound: signaling sender not bound")
	// ErrNotImplemented marks transfer/bridge paths not yet wired.
	ErrNotImplemented = errors.New("sip/outbound: not implemented")
)

Functions

This section is empty.

Types

type DialEvent

type DialEvent struct {
	CallID        string
	CorrelationID string
	Scenario      Scenario
	State         string
	StatusCode    int
	Reason        string
	At            time.Time
	RequestURI    string
	StatusText    string
	RemoteAddr    string
}

DialEvent streams dial lifecycle transitions.

type DialRequest

type DialRequest struct {
	Scenario      Scenario
	Target        DialTarget
	CorrelationID string
	ScriptID      string

	CallerUser        string
	CallerDisplayName string

	AssertedIdentityURI         string
	AssertedIdentityDisplayName string
	PrivacyTokens               []string
	HistoryInfo                 []historyinfo.Entry
	Diversion                   []historyinfo.Diversion

	// RTPPort is advertised in the SDP offer only (signaling demo default 10000).
	RTPPort int
	// Codecs overrides the default outbound offer list when non-empty.
	Codecs []sdp.Codec
	// OfferSRTP adds SDES a=crypto to the SDP offer (RTP/SAVPF).
	OfferSRTP bool

	// CallID optionally pre-allocates the dialog Call-ID (VoiceServer
	// dial gate / CDR correlation). Empty → generated at Dial time.
	CallID string
	// SDPBody when non-empty is used verbatim as the INVITE body,
	// bypassing auto SDP generation (custom SRTP/DTLS offers).
	SDPBody string
	// IdentityHeader is a pre-rendered RFC 8224 Identity header value.
	IdentityHeader string
}

DialRequest is one outbound signaling attempt (no media binding).

type DialTarget

type DialTarget struct {
	RequestURI        string // e.g. sip:1001@192.168.1.10;user=phone
	SignalingAddr     string // host:port of proxy or UAS
	CallerUser        string
	CallerDisplayName string
	Transport         Transport
}

DialTarget describes the next SIP hop for an outbound INVITE.

func DialTargetFromReferTo

func DialTargetFromReferTo(referTo string) (DialTarget, error)

DialTargetFromReferTo parses a Refer-To header value (possibly angle-bracketed) into a DialTarget. Host and port for SignalingAddr come from the SIP URI authority; default port is 5060 when omitted.

type EstablishedLeg

type EstablishedLeg struct {
	CallID        string
	Scenario      Scenario
	CorrelationID string
	CreatedAt     time.Time

	FromHeader          string
	ToHeader            string
	RemoteSignalingAddr string
	CSeqInvite          string

	Answer *sdp.Info
}

EstablishedLeg is delivered after 200 OK + ACK (signaling complete).

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager owns outbound SIP legs keyed by Call-ID.

func NewManager

func NewManager(cfg ManagerConfig) *Manager

NewManager constructs a manager; call BindSender before Dial.

func (*Manager) BindSender

func (m *Manager) BindSender(s SignalingSender)

BindSender wires the UDP signaling path (required for Dial).

func (*Manager) CleanupLegIfPresent

func (m *Manager) CleanupLegIfPresent(callID, reasonClass string)

CleanupLegIfPresent removes outbound leg state when no longer needed, e.g. after remote BYE. reasonClass is the bounded enum extracted from the peer's RFC 3326 Reason header (empty string → default "normal"). Both the BYE counter and the CDR hangup_reason inherit this value.

func (*Manager) ClosePool

func (m *Manager) ClosePool() error

ClosePool shuts pooled TCP/TLS connections.

func (*Manager) Dial

func (m *Manager) Dial(ctx context.Context, req DialRequest) (callID string, err error)

Dial starts an outbound INVITE. Returns Call-ID on success.

func (*Manager) DropLeg added in v1.4.3

func (m *Manager) DropLeg(callID string) bool

DropLeg removes local signaling state without sending SIP. Use after CANCEL was sent locally and the transaction should be torn down immediately.

func (*Manager) HandleSIPResponse

func (m *Manager) HandleSIPResponse(resp *stack.Message, addr *net.UDPAddr)

HandleSIPResponse routes inbound responses to outbound legs.

func (*Manager) IsLegEstablished added in v1.4.3

func (m *Manager) IsLegEstablished(callID string) bool

IsLegEstablished reports whether the outbound dialog reached 200 OK + ACK.

func (*Manager) SendBYE

func (m *Manager) SendBYE(callID string) error

SendBYE sends an in-dialog BYE for an established outbound leg (after 200 OK to INVITE).

func (*Manager) SendCANCEL

func (m *Manager) SendCANCEL(callID string) error

SendCANCEL emits a SIP CANCEL for a not-yet-established outbound INVITE leg (transfer-agent ring, campaign abandon, etc). Safe to call concurrently with the response handler; idempotent (second call is a no-op).

Behavior:

  • if the INVITE has not yet received its first 1xx provisional, the CANCEL is QUEUED (RFC 3261 §9.1) and fires either when 1xx arrives or after a 500ms grace timer, whichever comes first
  • if 1xx already received, CANCEL goes on the wire immediately and a retransmit goroutine starts (T1=500ms, capped at T2=4s, 6 attempts) per RFC 3261 §17.1.2
  • second call is a no-op (cancelSent CompareAndSwap guard) so the inbound-BYE path and ring-timeout path can both invoke safely

Returns nil on success (queued or sent). Common error reasons:

  • unknown call-id (leg already torn down / never existed)
  • leg already established (CANCEL is illegal — caller should use SendBYE instead)

type ManagerConfig

type ManagerConfig struct {
	LocalIP         string // SDP c= line
	SIPHost         string // Via / Contact host
	SIPPort         int
	FromUser        string
	FromDisplayName string
	DefaultRTPPort  int

	OnEstablished         func(EstablishedLeg)
	OnEvent               func(DialEvent)
	OnDialogCallIDAdopted func(oldID, newID, correlationID string)
	OnSignalingSent       func(*stack.Message, *net.UDPAddr)
	// PreAck runs after 200 OK SDP parse, before ACK (media binding).
	PreAck PreAckFunc
	// OnLegRemoved fires when a signaling leg is torn down (any cause).
	OnLegRemoved func(callID string)
	TLSConfig    *tls.Config
}

ManagerConfig configures outbound signaling (no RTP/media).

type PreAckContext added in v1.4.3

type PreAckContext struct {
	Leg            EstablishedLeg
	Answer         *sdp.Info
	ResponseSource *net.UDPAddr
}

PreAckContext is passed to ManagerConfig.PreAck after a 200 OK INVITE answer is parsed and before the UAC sends ACK.

type PreAckFunc added in v1.4.3

type PreAckFunc func(ctx context.Context, p PreAckContext) error

PreAckFunc runs media negotiation before ACK. Non-nil error aborts the leg without sending ACK.

type Scenario

type Scenario string

Scenario classifies why an outbound leg exists.

const (
	ScenarioCampaign      Scenario = "campaign"
	ScenarioTransferAgent Scenario = "transfer_agent"
	ScenarioCallback      Scenario = "callback"
	ScenarioManual        Scenario = "manual"
)

type SignalingSender

type SignalingSender interface {
	SendSIP(msg *stack.Message, addr *net.UDPAddr) error
}

SignalingSender sends SIP on a shared UDP socket.

type Transport

type Transport string

Transport identifies the SIP signaling transport for an outbound leg. Stringer values are the lowercase RFC 3261 §7.1 form used in Via headers and Request-URI ;transport= parameters.

const (
	// TransportUDP is unreliable datagram (RFC 3261 §18.1). Default.
	TransportUDP Transport = "udp"
	// TransportTCP is connection-oriented byte stream framed by
	// Content-Length (RFC 3261 §18.2). Connection reuse follows
	// RFC 5923.
	TransportTCP Transport = "tcp"
	// TransportTLS is TCP wrapped in TLS, paired with the SIPS URI
	// scheme on Contact / Via (RFC 3261 §26.2.1).
	TransportTLS Transport = "tls"
	// TransportUnset means "no preference" — selection falls through
	// to the next precedence layer or to TransportUDP.
	TransportUnset Transport = ""
)

func ResolveTransport

func ResolveTransport(target DialTarget) Transport

ResolveTransport applies the precedence rules above:

URI param > target.Transport (trunk config) > TransportUDP

Always returns a valid (non-Unset) transport.

func (Transport) IsConnectionOriented

func (t Transport) IsConnectionOriented() bool

IsConnectionOriented reports whether the transport requires per-target conn lifecycle management (TCP/TLS) versus the shared-socket model (UDP).

func (Transport) IsTLS

func (t Transport) IsTLS() bool

IsTLS reports whether the transport requires TLS handshaking. The caller should also use the `sips:` URI scheme in Contact / Via when this returns true (RFC 3261 §26.2.1).

func (Transport) IsValid

func (t Transport) IsValid() bool

IsValid reports whether t is one of the recognised transports. TransportUnset is NOT valid (caller should resolve before calling).

func (Transport) ViaToken

func (t Transport) ViaToken() string

ViaToken returns the Via header transport token, e.g. "SIP/2.0/UDP". Always uppercase per RFC 3261 §7.1.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL