Skip to content
Merged
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
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require (
github.com/jfreymuth/oggvorbis v1.0.5
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1
github.com/livekit/mediatransportutil v0.0.0-20241220010243-a2bdee945564
github.com/livekit/protocol v1.34.1-0.20250227175732-e8f463c43a44
github.com/livekit/protocol v1.34.1-0.20250301124708-e35a55f14851
github.com/livekit/psrpc v0.6.1-0.20250205181828-a0beed2e4126
github.com/livekit/server-sdk-go/v2 v2.5.0
github.com/livekit/sipgo v0.13.2-0.20250130142851-36ed3228d934
Expand Down Expand Up @@ -61,7 +61,7 @@ require (
github.com/emiago/sipgo v0.24.1 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/gammazero/deque v1.0.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gammazero/deque v1.0.0 h1:LTmimT8H7bXkkCy6gZX7zNLtkbz4NdS2z8LZuor3j34=
github.com/gammazero/deque v1.0.0/go.mod h1:iflpYvtGfM3U8S8j+sZEKIak3SAKYpA5/SQewgfXDKo=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
Expand Down Expand Up @@ -127,8 +127,8 @@ github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 h1:jm09419p0lqTkD
github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ=
github.com/livekit/mediatransportutil v0.0.0-20241220010243-a2bdee945564 h1:GX7KF/V9ExmcfT/2Bdia8aROjkxrgx7WpyH7w9MB4J4=
github.com/livekit/mediatransportutil v0.0.0-20241220010243-a2bdee945564/go.mod h1:36s+wwmU3O40IAhE+MjBWP3W71QRiEE9SfooSBvtBqY=
github.com/livekit/protocol v1.34.1-0.20250227175732-e8f463c43a44 h1:Cr8s9Vwm72S6Wu0TAiFnuXopnjL/aA0JhNgsz3AH7CI=
github.com/livekit/protocol v1.34.1-0.20250227175732-e8f463c43a44/go.mod h1:yXuQ7ucrLj91nbxL6/AHgtxdha1DGzLj1LkgvnT90So=
github.com/livekit/protocol v1.34.1-0.20250301124708-e35a55f14851 h1:avsQZ/oaPS3PZnnKcqsggrkg0k31sXmI+swuRhVEweE=
github.com/livekit/protocol v1.34.1-0.20250301124708-e35a55f14851/go.mod h1:WrT/CYRxtMNOVUjnIPm5OjWtEkmreffTeE1PRZwlRg4=
github.com/livekit/psrpc v0.6.1-0.20250205181828-a0beed2e4126 h1:fzuYpAQbCid7ySPpQWWePfQOWUrs8x6dJ0T3Wl07n+Y=
github.com/livekit/psrpc v0.6.1-0.20250205181828-a0beed2e4126/go.mod h1:X5WtEZ7OnEs72Fi5/J+i0on3964F1aynQpCalcgMqRo=
github.com/livekit/server-sdk-go/v2 v2.5.0 h1:HCKm3f6PvefGp8emNC2mi9+9IXzBYrynuGbtUdp5u+w=
Expand Down
35 changes: 20 additions & 15 deletions pkg/service/psrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package service
import (
"context"
"fmt"
"net/netip"

"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/rpc"
Expand All @@ -12,15 +11,18 @@ import (
"github.com/livekit/sip/pkg/sip"
)

func GetAuthCredentials(ctx context.Context, psrpcClient rpc.IOInfoClient, callID, from, to, toHost string, srcAddress netip.Addr) (sip.AuthInfo, error) {
func GetAuthCredentials(ctx context.Context, psrpcClient rpc.IOInfoClient, call *rpc.SIPCall) (sip.AuthInfo, error) {
ctx, span := tracer.Start(ctx, "service.GetAuthCredentials")
defer span.End()
resp, err := psrpcClient.GetSIPTrunkAuthentication(ctx, &rpc.GetSIPTrunkAuthenticationRequest{
SipCallId: callID,
From: from,
To: to,
ToHost: toHost,
SrcAddress: srcAddress.String(),
Call: call,

SipCallId: call.LkCallId,
From: call.From.User,
FromHost: call.From.Host,
To: call.To.User,
ToHost: call.To.Host,
SrcAddress: call.SourceIp,
})

if err != nil {
Expand Down Expand Up @@ -52,14 +54,17 @@ func DispatchCall(ctx context.Context, psrpcClient rpc.IOInfoClient, log logger.
ctx, span := tracer.Start(ctx, "service.DispatchCall")
defer span.End()
resp, err := psrpcClient.EvaluateSIPDispatchRules(ctx, &rpc.EvaluateSIPDispatchRulesRequest{
SipCallId: info.ID,
SipTrunkId: info.TrunkID,
CallingNumber: info.FromUser,
CalledNumber: info.ToUser,
CalledHost: info.ToHost,
SrcAddress: info.SrcAddress.String(),
Pin: info.Pin,
NoPin: info.NoPin,
SipTrunkId: info.TrunkID,
Call: info.Call,
Pin: info.Pin,
NoPin: info.NoPin,

SipCallId: info.Call.LkCallId,
CallingNumber: info.Call.From.User,
CallingHost: info.Call.From.Host,
CalledNumber: info.Call.To.User,
CalledHost: info.Call.To.Host,
SrcAddress: info.Call.SourceIp,
})

if err != nil {
Expand Down
5 changes: 2 additions & 3 deletions pkg/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"net"
"net/http"
"net/http/pprof"
"net/netip"
"sync/atomic"
"time"

Expand Down Expand Up @@ -171,8 +170,8 @@ func (s *Service) Run() error {
}
}

func (s *Service) GetAuthCredentials(ctx context.Context, callID, from, to, toHost string, srcAddress netip.Addr) (sip.AuthInfo, error) {
return GetAuthCredentials(ctx, s.psrpcClient, callID, from, to, toHost, srcAddress)
func (s *Service) GetAuthCredentials(ctx context.Context, call *rpc.SIPCall) (sip.AuthInfo, error) {
return GetAuthCredentials(ctx, s.psrpcClient, call)
}

func (s *Service) DispatchCall(ctx context.Context, info *sip.CallInfo) sip.CallDispatch {
Expand Down
62 changes: 40 additions & 22 deletions pkg/sip/inbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package sip
import (
"context"
"fmt"
"github.com/livekit/protocol/rpc"
"math"
"net/netip"
"slices"
Expand Down Expand Up @@ -184,7 +185,7 @@ func (s *Server) processInvite(req *sip.Request, tx sip.ServerTransaction) (retE

from, to := cc.From(), cc.To()

cmon := s.mon.NewCall(stats.Inbound, from.Host, cc.To().Host)
cmon := s.mon.NewCall(stats.Inbound, from.Host, to.Host)
cmon.InviteReq()
defer cmon.SessionDur()()
joinDur := cmon.JoinDur()
Expand All @@ -193,7 +194,25 @@ func (s *Server) processInvite(req *sip.Request, tx sip.ServerTransaction) (retE
cc.Processing()
}

r, err := s.handler.GetAuthCredentials(ctx, callID, from.User, to.User, to.Host, src.Addr())
callInfo := &rpc.SIPCall{
LkCallId: callID,
SourceIp: src.Addr().String(),
Address: ToSIPUri("", cc.Address()),
From: ToSIPUri("", from),
To: ToSIPUri("", to),
}
for _, h := range cc.RemoteHeaders() {
switch h := h.(type) {
case *sip.ViaHeader:
callInfo.Via = append(callInfo.Via, &livekit.SIPUri{
Host: h.Host,
Port: uint32(h.Port),
Transport: SIPTransportFrom(Transport(h.Transport)),
})
}
}

r, err := s.handler.GetAuthCredentials(ctx, callInfo)
if err != nil {
cmon.InviteErrorShort("auth-error")
log.Warnw("Rejecting inbound, auth check failed", err)
Expand Down Expand Up @@ -245,7 +264,7 @@ func (s *Server) processInvite(req *sip.Request, tx sip.ServerTransaction) (retE
// ok
}

call = s.newInboundCall(log, cmon, cc, src, state, nil)
call = s.newInboundCall(log, cmon, cc, callInfo, state, nil)
call.joinDur = joinDur
return call.handleInvite(call.ctx, req, r.TrunkID, s.conf)
}
Expand Down Expand Up @@ -316,7 +335,7 @@ type inboundCall struct {
attrsToHdr map[string]string
ctx context.Context
cancel func()
src netip.AddrPort
call *rpc.SIPCall
media *MediaPort
dtmf chan dtmf.Event // buffered
lkRoom *Room // LiveKit room; only active after correct pin is entered
Expand All @@ -331,7 +350,7 @@ func (s *Server) newInboundCall(
log logger.Logger,
mon *stats.CallMonitor,
cc *sipInbound,
src netip.AddrPort,
call *rpc.SIPCall,
state *CallState,
extra map[string]string,
) *inboundCall {
Expand All @@ -342,7 +361,7 @@ func (s *Server) newInboundCall(
log: log,
mon: mon,
cc: cc,
src: src,
call: call,
state: state,
extraAttrs: extra,
dtmf: make(chan dtmf.Event, 10),
Expand All @@ -366,14 +385,10 @@ func (c *inboundCall) handleInvite(ctx context.Context, req *sip.Request, trunkI
// Send initial request. In the best case scenario, we will immediately get a room name to join.
// Otherwise, we could even learn that this number is not allowed and reject the call, or ask for pin if required.
disp := c.s.handler.DispatchCall(ctx, &CallInfo{
TrunkID: trunkID,
ID: string(c.cc.ID()),
FromUser: c.cc.From().User,
ToUser: c.cc.To().User,
ToHost: c.cc.To().Host,
SrcAddress: c.src.Addr(),
Pin: "",
NoPin: false,
TrunkID: trunkID,
Call: c.call,
Pin: "",
NoPin: false,
})
if disp.ProjectID != "" {
c.log = c.log.WithValues("projectID", disp.ProjectID)
Expand Down Expand Up @@ -659,14 +674,10 @@ func (c *inboundCall) pinPrompt(ctx context.Context, trunkID string) (disp CallD

c.log.Infow("Checking Pin for SIP call", "pin", pin, "noPin", noPin)
disp = c.s.handler.DispatchCall(ctx, &CallInfo{
TrunkID: trunkID,
ID: string(c.cc.ID()),
FromUser: c.cc.From().User,
ToUser: c.cc.To().User,
ToHost: c.cc.To().Host,
SrcAddress: c.src.Addr(),
Pin: pin,
NoPin: noPin,
TrunkID: trunkID,
Call: c.call,
Pin: pin,
NoPin: noPin,
})
if disp.ProjectID != "" {
c.log = c.log.WithValues("projectID", disp.ProjectID)
Expand Down Expand Up @@ -1008,6 +1019,13 @@ func (c *sipInbound) RespondAndDrop(status sip.StatusCode, reason string) {
c.drop()
}

func (c *sipInbound) Address() sip.Uri {
if c.invite == nil {
return sip.Uri{}
}
return c.invite.Recipient
}

func (c *sipInbound) From() sip.Uri {
if c.from == nil {
return sip.Uri{}
Expand Down
9 changes: 9 additions & 0 deletions pkg/sip/outbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,15 @@ func (c *sipOutbound) To() sip.Uri {
return c.to.Address
}

func (c *sipOutbound) Address() sip.Uri {
c.mu.RLock()
defer c.mu.RUnlock()
if c.invite == nil {
return sip.Uri{}
}
return c.invite.Recipient
}

func (c *sipOutbound) ID() LocalTag {
return c.id
}
Expand Down
14 changes: 14 additions & 0 deletions pkg/sip/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/pkg/errors"

"github.com/livekit/protocol/livekit"
"github.com/livekit/psrpc"
"github.com/livekit/sipgo/sip"

Expand Down Expand Up @@ -107,6 +108,7 @@ func statusName(status int) string {
type setHeadersFunc func(headers map[string]string) map[string]string

type Signaling interface {
Address() sip.Uri
From() sip.Uri
To() sip.Uri
ID() LocalTag
Expand Down Expand Up @@ -363,3 +365,15 @@ func setCSeq(req *sip.Request, cseq uint32) {
req.RemoveHeader(h.Name())
req.AppendHeader(h)
}

func ToSIPUri(ip string, u sip.Uri) *livekit.SIPUri {
tr, _ := u.UriParams.Get("transport")
url := &livekit.SIPUri{
User: u.User,
Host: u.Host,
Ip: ip,
Port: uint32(u.Port),
Transport: SIPTransportFrom(Transport(tr)),
}
return url
}
15 changes: 6 additions & 9 deletions pkg/sip/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"github.com/livekit/protocol/rpc"
"io"
"log/slog"
"net"
Expand Down Expand Up @@ -50,14 +51,10 @@ var (
)

type CallInfo struct {
TrunkID string
ID string
FromUser string
ToUser string
ToHost string
SrcAddress netip.Addr
Pin string
NoPin bool
TrunkID string
Call *rpc.SIPCall
Pin string
NoPin bool
}

type AuthResult int
Expand Down Expand Up @@ -102,7 +99,7 @@ type CallDispatch struct {
}

type Handler interface {
GetAuthCredentials(ctx context.Context, callID, fromUser, toUser, toHost string, srcAddress netip.Addr) (AuthInfo, error)
GetAuthCredentials(ctx context.Context, call *rpc.SIPCall) (AuthInfo, error)
DispatchCall(ctx context.Context, info *CallInfo) CallDispatch
GetMediaProcessor(features []livekit.SIPFeature) media.PCM16Processor

Expand Down
19 changes: 9 additions & 10 deletions pkg/sip/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"math/rand"
"net/netip"
"testing"
"time"

Expand Down Expand Up @@ -54,12 +53,12 @@ func expectNoResponse(t *testing.T, tx sip.ClientTransaction) {
}

type TestHandler struct {
GetAuthCredentialsFunc func(ctx context.Context, callID, fromUser, toUser, toHost string, srcAddress netip.Addr) (AuthInfo, error)
GetAuthCredentialsFunc func(ctx context.Context, call *rpc.SIPCall) (AuthInfo, error)
DispatchCallFunc func(ctx context.Context, info *CallInfo) CallDispatch
}

func (h TestHandler) GetAuthCredentials(ctx context.Context, callID, fromUser, toUser, toHost string, srcAddress netip.Addr) (AuthInfo, error) {
return h.GetAuthCredentialsFunc(ctx, callID, fromUser, toUser, toHost, srcAddress)
func (h TestHandler) GetAuthCredentials(ctx context.Context, call *rpc.SIPCall) (AuthInfo, error) {
return h.GetAuthCredentialsFunc(ctx, call)
}

func (h TestHandler) DispatchCall(ctx context.Context, info *CallInfo) CallDispatch {
Expand Down Expand Up @@ -132,9 +131,9 @@ func TestService_AuthFailure(t *testing.T) {
expectedToUser = "bar"
)
h := &TestHandler{
GetAuthCredentialsFunc: func(ctx context.Context, callID, fromUser, toUser, toHost string, srcAddress netip.Addr) (AuthInfo, error) {
require.Equal(t, expectedFromUser, fromUser)
require.Equal(t, expectedToUser, toUser)
GetAuthCredentialsFunc: func(ctx context.Context, call *rpc.SIPCall) (AuthInfo, error) {
require.Equal(t, expectedFromUser, call.From.User)
require.Equal(t, expectedToUser, call.To.User)
return AuthInfo{}, fmt.Errorf("Auth Failure")
},
}
Expand All @@ -153,9 +152,9 @@ func TestService_AuthDrop(t *testing.T) {
expectedToUser = "bar"
)
h := &TestHandler{
GetAuthCredentialsFunc: func(ctx context.Context, callID, fromUser, toUser, toHost string, srcAddress netip.Addr) (AuthInfo, error) {
require.Equal(t, expectedFromUser, fromUser)
require.Equal(t, expectedToUser, toUser)
GetAuthCredentialsFunc: func(ctx context.Context, call *rpc.SIPCall) (AuthInfo, error) {
require.Equal(t, expectedFromUser, call.From.User)
require.Equal(t, expectedToUser, call.To.User)
return AuthInfo{Result: AuthDrop}, nil
},
}
Expand Down
Loading