Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ import (
)

const (
DefaultSIPPort int = 5060
DefaultSIPPortTLS int = 5061
DefaultSIPPort int = 5060
DefaultSIPPortTLS int = 5061
DefaultRingingTimeoutStatus int = 486 // Busy Here
)

var (
Expand Down Expand Up @@ -94,6 +95,7 @@ type Config struct {
SIPHostname string `yaml:"sip_hostname"`
OutboundRouteHeaders []string `yaml:"outbound_route_headers"` // Route headers prepended to outbound requests, e.g. "<sip:proxy:5060;transport=tcp;lr>"
SIPRingingInterval time.Duration `yaml:"sip_ringing_interval"` // from 1 sec up to 60 (default '1s')
RingingTimeoutStatus int `yaml:"ringing_timeout_status"` // status when a call rings out, 3xx-6xx (default '486')
TCP *TCPConfig `yaml:"tcp"`
TLS *TLSConfig `yaml:"tls"`
RTPPort rtcconfig.PortRange `yaml:"rtp_port"`
Expand Down Expand Up @@ -208,6 +210,12 @@ func (c *Config) Init() error {
if c.MaxCpuUtilization <= 0 || c.MaxCpuUtilization > 1 {
c.MaxCpuUtilization = 0.9
}
if c.RingingTimeoutStatus == 0 {
c.RingingTimeoutStatus = DefaultRingingTimeoutStatus
} else if c.RingingTimeoutStatus < 300 || c.RingingTimeoutStatus > 699 {
return psrpc.NewErrorf(psrpc.InvalidArgument,
"ringing_timeout_status must be a SIP failure status between 300 and 699, got %d", c.RingingTimeoutStatus)
}

if err := c.InitLogger(); err != nil {
return err
Expand Down
9 changes: 8 additions & 1 deletion pkg/sip/inbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -1226,7 +1226,11 @@ func (c *inboundCall) waitSubscribe(ctx context.Context, timeout time.Duration)
c.close(ctx, end)
return false, psrpc.NewErrorf(psrpc.Canceled, "rpc terminated the call")
case <-timer.C:
c.closeWithTerm(ctx, stats.ServerError("cannot-subscribe"))
c.close(ctx, EndCall{
Status: callDropped,
Term: stats.ServerError("cannot-subscribe"),
Code: sip.StatusCode(c.s.conf.RingingTimeoutStatus),
})
return false, psrpc.NewErrorf(psrpc.DeadlineExceeded, "room subscription timed out")
case <-c.lkRoom.Subscribed():
return true, nil
Expand Down Expand Up @@ -1368,6 +1372,9 @@ func (c *inboundCall) close(ctx context.Context, end EndCall) {
Status: "Request Terminated",
}
}
if end.Code != 0 {
result = Result{Code: end.Code}
}
log := c.log().WithValues("status", result.Code, "result", string(end.Term.Result), "reason", end.Term.Reason)
defer func() {
c.stats.Update()
Expand Down
1 change: 1 addition & 0 deletions pkg/sip/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type EndCall struct {
Term stats.Termination
Reason livekit.DisconnectReason // disconnect reason for LiveKit participant
Headers map[string]string // extra headers to send to SIP peer
Code sip.StatusCode // SIP status for an unanswered call; zero picks the default
}

var statusNamesMap = map[int]string{
Expand Down
45 changes: 45 additions & 0 deletions pkg/sip/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,51 @@ func TestCANCELSendsBothResponses(t *testing.T) {
}
}

// A call that rings for the whole ringing timeout without the room ever subscribing
// is rejected with ringing_timeout_status, or 486 Busy Here when it is not configured.
func TestRingingTimeoutStatus(t *testing.T) {
cases := []struct {
name string
configed int
expected sip.StatusCode
}{
{name: "unconfigured", configed: 0, expected: sip.StatusBusyHere},
{name: "configured", configed: 480, expected: sip.StatusTemporarilyUnavailable},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
h := &TestHandler{
DispatchCallFunc: func(ctx context.Context, info *CallInfo) CallDispatch {
identity := fmt.Sprintf("test-participant-%s", info.Call.SipCallId)
return CallDispatch{
Result: DispatchAccept,
RingingTimeout: 500 * time.Millisecond,
Room: RoomConfig{
RoomName: "test-room",
Participant: ParticipantConfig{
Identity: identity,
Name: identity,
},
},
}
},
}
st := NewServiceTest(t, &serviceTestConfig{
GetRoom: newTestRoomConfig(&testRoomConfig{ringForever: true}),
Handler: h,
})
st.Server.conf.RingingTimeoutStatus = c.configed

call := newTestCall(st.TestUA, false)
req, _, err := call.Invite(nil)
require.NoError(t, err)

resp := st.TestUA.TransactionRequest(t, req, true)
require.Equal(t, c.expected, resp.StatusCode)
})
}
}

// TestSameCallIDForAuthFlow verifies that the same LiveKit call ID is assigned to both
// the initial INVITE (without auth) and the subsequent INVITE (with auth)
func TestSameCallIDForAuthFlow(t *testing.T) {
Expand Down
6 changes: 5 additions & 1 deletion pkg/sip/signaling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ type serviceTest struct {

type serviceTestConfig struct {
GetRoom GetRoomFunc
Handler Handler
}

func NewServiceTest(t *testing.T, options *serviceTestConfig) *serviceTest {
Expand All @@ -434,6 +435,9 @@ func NewServiceTest(t *testing.T, options *serviceTestConfig) *serviceTest {
if options.GetRoom == nil {
options.GetRoom = newTestRoomConfig(nil)
}
if options.Handler == nil {
options.Handler = &TestHandler{}
}

sipPort := rand.Intn(testPortSIPMax-testPortSIPMin) + testPortSIPMin
loopback := netip.MustParseAddr("127.0.0.1")
Expand Down Expand Up @@ -492,7 +496,7 @@ func NewServiceTest(t *testing.T, options *serviceTestConfig) *serviceTest {
MediaIP: loopback,
}

handler := &TestHandler{}
handler := options.Handler

err = srv.Start(nil, sconf, nil, cli.OnRequest)
require.NoError(t, err)
Expand Down