Skip to content

Commit 789bca4

Browse files
authored
Parse the BYE reason. (#471)
* Parse the BYE reason. * Fix REFER test. * Only populate error for non-normal bye reasons.
1 parent 75ff0fc commit 789bca4

File tree

4 files changed

+141
-9
lines changed

4 files changed

+141
-9
lines changed

pkg/sip/inbound.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"math"
2424
"net/netip"
2525
"slices"
26+
"strings"
2627
"sync"
2728
"sync/atomic"
2829
"time"
@@ -464,9 +465,25 @@ func (s *Server) onBye(log *slog.Logger, req *sip.Request, tx sip.ServerTransact
464465
c := s.activeCalls[tag]
465466
s.cmu.RUnlock()
466467
if c != nil {
467-
c.log.Infow("BYE from remote")
468468
c.cc.AcceptBye(req, tx)
469-
_ = c.Close()
469+
var (
470+
reason ReasonHeader
471+
rawReason string
472+
)
473+
if h := req.GetHeader("Reason"); h != nil {
474+
rawReason = h.Value()
475+
reason, err = ParseReasonHeader(rawReason)
476+
if err != nil {
477+
c.log.Warnw("cannot parse reason header", err, "reason-raw", rawReason)
478+
}
479+
}
480+
c.log.Infow("BYE from remote",
481+
"reason-type", reason.Type,
482+
"reason-cause", reason.Cause,
483+
"reason-text", reason.Text,
484+
"reason-raw", rawReason,
485+
)
486+
c.Bye(reason)
470487
return
471488
}
472489
ok := false
@@ -540,6 +557,7 @@ type inboundCall struct {
540557
attrsToHdr map[string]string
541558
ctx context.Context
542559
cancel func()
560+
closeReason atomic.Pointer[ReasonHeader]
543561
call *rpc.SIPCall
544562
media *MediaPort
545563
dtmf chan dtmf.Event // buffered
@@ -1062,17 +1080,41 @@ func (c *inboundCall) closeWithNoACK() {
10621080
}
10631081

10641082
func (c *inboundCall) closeWithCancelled() {
1065-
c.state.DeferUpdate(func(info *livekit.SIPCallInfo) {
1066-
info.DisconnectReason = livekit.DisconnectReason_CLIENT_INITIATED
1067-
})
1068-
c.close(false, CallHangup, "cancelled")
1083+
var reason ReasonHeader
1084+
if p := c.closeReason.Load(); p != nil {
1085+
reason = *p
1086+
}
1087+
c.closeWithReason(CallHangup, "cancelled", reason)
10691088
}
10701089

10711090
func (c *inboundCall) closeWithHangup() {
1091+
var reason ReasonHeader
1092+
if p := c.closeReason.Load(); p != nil {
1093+
reason = *p
1094+
}
1095+
c.closeWithReason(CallHangup, "hangup", reason)
1096+
}
1097+
1098+
func (c *inboundCall) closeWithReason(status CallStatus, reasonName string, reason ReasonHeader) {
10721099
c.state.DeferUpdate(func(info *livekit.SIPCallInfo) {
10731100
info.DisconnectReason = livekit.DisconnectReason_CLIENT_INITIATED
1101+
if info.Error == "" {
1102+
if !reason.IsNormal() {
1103+
info.Error = reason.String()
1104+
}
1105+
}
10741106
})
1075-
c.close(false, CallHangup, "hangup")
1107+
if reason.Type != "" {
1108+
if !reason.IsNormal() {
1109+
reasonName = fmt.Sprintf("bye-%s-%d", strings.ToLower(reason.Type), reason.Cause)
1110+
}
1111+
}
1112+
c.close(false, status, reasonName)
1113+
}
1114+
1115+
func (c *inboundCall) Bye(reason ReasonHeader) {
1116+
c.closeReason.Store(&reason)
1117+
_ = c.Close()
10761118
}
10771119

10781120
func (c *inboundCall) Close() error {

pkg/sip/protocol.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,3 +456,59 @@ func ToSIPUri(ip string, u sip.Uri) *livekit.SIPUri {
456456
}
457457
return url
458458
}
459+
460+
type ReasonHeader struct {
461+
Type string
462+
Cause int
463+
Text string
464+
}
465+
466+
func (r ReasonHeader) IsZero() bool {
467+
return r == ReasonHeader{}
468+
}
469+
470+
func (r ReasonHeader) IsNormal() bool {
471+
if r.IsZero() {
472+
return true // assume there's no specific reason
473+
}
474+
switch r.Type {
475+
case "Q.850":
476+
switch r.Cause {
477+
case 16: // Normal call clearing
478+
return true
479+
}
480+
}
481+
return false
482+
}
483+
484+
func (r ReasonHeader) String() string {
485+
if r.IsZero() {
486+
return "<none>"
487+
}
488+
return fmt.Sprintf("%s-%d: %s", r.Type, r.Cause, r.Text)
489+
}
490+
491+
func ParseReasonHeader(header string) (ReasonHeader, error) {
492+
list := strings.Split(header, ";")
493+
if len(list) < 2 {
494+
return ReasonHeader{}, errors.New("no fields in the reason")
495+
}
496+
typ := strings.TrimSpace(list[0])
497+
r := ReasonHeader{Type: typ}
498+
for _, line := range list[1:] {
499+
line = strings.TrimSpace(line)
500+
i := strings.Index(line, "=")
501+
if i < 0 {
502+
continue
503+
}
504+
key := strings.TrimSpace(line[:i])
505+
val := strings.TrimSpace(line[i+1:])
506+
switch key {
507+
case "cause":
508+
r.Cause, _ = strconv.Atoi(val)
509+
case "text":
510+
r.Text, _ = strconv.Unquote(val)
511+
}
512+
}
513+
return r, nil
514+
}

pkg/sip/protocol_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,37 @@ func TestHandleNotify(t *testing.T) {
7272
m, c, s, r, err = handleNotify(req)
7373
require.Error(t, err)
7474
}
75+
76+
func TestParseReason(t *testing.T) {
77+
cases := []struct {
78+
Name string
79+
Header string
80+
Reason ReasonHeader
81+
}{
82+
{
83+
Name: "SIP",
84+
Header: `SIP ;cause=200 ;text="Call completed elsewhere"`,
85+
Reason: ReasonHeader{
86+
Type: "SIP",
87+
Cause: 200,
88+
Text: "Call completed elsewhere",
89+
},
90+
},
91+
{
92+
Name: "Q.850",
93+
Header: `Q.850;cause=16;text="Terminated"`,
94+
Reason: ReasonHeader{
95+
Type: "Q.850",
96+
Cause: 16,
97+
Text: "Terminated",
98+
},
99+
},
100+
}
101+
for _, c := range cases {
102+
t.Run(c.Name, func(t *testing.T) {
103+
r, err := ParseReasonHeader(c.Header)
104+
require.NoError(t, err)
105+
require.Equal(t, c.Reason, r)
106+
})
107+
}
108+
}

test/integration/sip_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ func TestSIPJoinOpenRoom(t *testing.T) {
421421

422422
require.Equal(t, sipgo.REFER, referRequest.Method)
423423
transferTo := referRequest.GetHeader("Refer-To")
424-
require.Equal(t, "tel:"+transferNumber, transferTo.Value())
424+
require.Equal(t, "<tel:"+transferNumber+">", transferTo.Value())
425425

426426
time.Sleep(notifyIntervalDelay)
427427
err = cli.SendNotify(referRequest, "SIP/2.0 100 Trying")
@@ -577,7 +577,7 @@ func TestSIPJoinPinRoom(t *testing.T) {
577577

578578
require.Equal(t, sipgo.REFER, referRequest.Method)
579579
transferTo := referRequest.GetHeader("Refer-To")
580-
require.Equal(t, "tel:"+transferNumber, transferTo.Value())
580+
require.Equal(t, "<tel:"+transferNumber+">", transferTo.Value())
581581

582582
time.Sleep(notifyIntervalDelay)
583583
err = cli.SendNotify(referRequest, "SIP/2.0 403 Fobidden")

0 commit comments

Comments
 (0)