Skip to content

Commit d02bf0c

Browse files
authored
Fix outbound destination setting to honor Route headers (#489)
* route fix * Dropping setDestination * Adding integration test
1 parent 39d6dd7 commit d02bf0c

File tree

2 files changed

+115
-4
lines changed

2 files changed

+115
-4
lines changed

pkg/sip/outbound.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,6 @@ func (c *sipOutbound) Invite(ctx context.Context, to URI, user, pass string, hea
756756
defer c.mu.Unlock()
757757
toHeader := &sip.ToHeader{Address: *to.GetURI()}
758758

759-
dest := to.GetDest()
760759
c.callID = guid.HashedID(fmt.Sprintf("%s-%s", string(c.id), toHeader.Address.String()))
761760
c.log = c.log.WithValues("sipCallID", c.callID)
762761

@@ -779,7 +778,7 @@ authLoop:
779778
if try >= 5 {
780779
return nil, fmt.Errorf("max auth retry attemps reached")
781780
}
782-
req, resp, err = c.attemptInvite(ctx, sip.CallIDHeader(c.callID), dest, toHeader, sdpOffer, authHeaderRespName, authHeader, sipHeaders, setState)
781+
req, resp, err = c.attemptInvite(ctx, sip.CallIDHeader(c.callID), toHeader, sdpOffer, authHeaderRespName, authHeader, sipHeaders, setState)
783782
if err != nil {
784783
return nil, err
785784
}
@@ -889,15 +888,14 @@ func (c *sipOutbound) AckInviteOK(ctx context.Context) error {
889888
return c.c.sipCli.WriteRequest(sip.NewAckRequest(c.invite, c.inviteOk, nil))
890889
}
891890

892-
func (c *sipOutbound) attemptInvite(ctx context.Context, callID sip.CallIDHeader, dest string, to *sip.ToHeader, offer []byte, authHeaderName, authHeader string, headers Headers, setState sipRespFunc) (*sip.Request, *sip.Response, error) {
891+
func (c *sipOutbound) attemptInvite(ctx context.Context, callID sip.CallIDHeader, to *sip.ToHeader, offer []byte, authHeaderName, authHeader string, headers Headers, setState sipRespFunc) (*sip.Request, *sip.Response, error) {
893892
ctx, span := tracer.Start(ctx, "sipOutbound.attemptInvite")
894893
defer span.End()
895894
req := sip.NewRequest(sip.INVITE, to.Address)
896895
c.setCSeq(req)
897896
req.RemoveHeader("Call-ID")
898897
req.AppendHeader(&callID)
899898

900-
req.SetDestination(dest)
901899
req.SetBody(offer)
902900
req.AppendHeader(to)
903901
req.AppendHeader(c.from)

test/integration/sip_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"log/slog"
77
"math/rand"
8+
"net"
89
"net/netip"
910
"os"
1011
"strconv"
@@ -945,3 +946,115 @@ func TestSIPOutbound(t *testing.T) {
945946
})
946947
}
947948
}
949+
950+
func TestSIPOutboundRouteHeader(t *testing.T) {
951+
// Test that when a Route header is specified in CreateSIPParticipant request,
952+
// the SIP message is sent to the route header target instead of the request URI.
953+
954+
// Set up two LiveKit servers and SIP servers
955+
lkOut := runLiveKit(t)
956+
lkIn := runLiveKit(t)
957+
srvOut := runSIPServer(t, lkOut)
958+
srvIn := runSIPServer(t, lkIn)
959+
960+
const (
961+
roomIn = "inbound"
962+
userName = "test-user"
963+
userPass = "test-pass"
964+
meta = `{"test":true}`
965+
)
966+
967+
// Configure Trunk for inbound server
968+
trunkIn := srvIn.CreateTrunkIn(t, &livekit.SIPInboundTrunkInfo{
969+
Name: "Test In",
970+
Numbers: []string{serverNumber},
971+
AuthUsername: userName,
972+
AuthPassword: userPass,
973+
})
974+
t.Cleanup(func() {
975+
srvIn.DeleteTrunk(t, trunkIn)
976+
})
977+
978+
ruleIn := srvIn.CreateDirectDispatch(t, roomIn, "", meta, nil)
979+
t.Cleanup(func() {
980+
srvIn.DeleteDispatch(t, ruleIn)
981+
})
982+
983+
// Create a mock SIP server that will receive the route header target
984+
// This server should be different from the request URI
985+
routeTarget := "127.0.0.1:5061" // Different from srvIn.Address
986+
987+
// Set up a mock SIP server to capture the route header target
988+
// We'll create a simple UDP server that can receive and log the SIP message
989+
routeServer, err := net.ListenPacket("udp", routeTarget)
990+
require.NoError(t, err)
991+
defer routeServer.Close()
992+
993+
// Channel to capture received messages
994+
receivedMessages := make(chan string, 10)
995+
996+
// Start a goroutine to listen for messages on the route target
997+
go func() {
998+
buffer := make([]byte, 1024)
999+
for {
1000+
n, addr, err := routeServer.ReadFrom(buffer)
1001+
if err != nil {
1002+
return
1003+
}
1004+
message := string(buffer[:n])
1005+
receivedMessages <- message
1006+
t.Logf("Route server received message from %s: %s", addr, message)
1007+
}
1008+
}()
1009+
1010+
// Configure Trunk for outbound server with the request URI (different from route target)
1011+
trunkOut := srvOut.CreateTrunkOut(t, &livekit.SIPOutboundTrunkInfo{
1012+
Name: "Test Out",
1013+
Numbers: []string{clientNumber},
1014+
Address: srvIn.Address, // This will be the request URI
1015+
Transport: livekit.SIPTransport_SIP_TRANSPORT_UDP,
1016+
AuthUsername: userName,
1017+
AuthPassword: userPass,
1018+
})
1019+
t.Cleanup(func() {
1020+
srvOut.DeleteTrunk(t, trunkOut)
1021+
})
1022+
1023+
// Create the outbound SIP participant with a Route header
1024+
// The Route header should point to a different destination than the request URI
1025+
routeHeader := fmt.Sprintf("<sip:%s;lr>", routeTarget)
1026+
1027+
// Create the SIP participant with the Route header
1028+
// We need to pass the Route header in the headers map
1029+
headers := map[string]string{
1030+
"Route": routeHeader,
1031+
}
1032+
1033+
// Create the outbound SIP participant with the Route header
1034+
t.Logf("Testing Route header: %s", routeHeader)
1035+
t.Logf("Request URI target: %s", srvIn.Address)
1036+
t.Logf("Route header target: %s", routeTarget)
1037+
1038+
// Create the outbound SIP participant
1039+
r := lkOut.CreateSIPParticipant(t, &livekit.CreateSIPParticipantRequest{
1040+
SipTrunkId: trunkOut,
1041+
SipCallTo: serverNumber,
1042+
RoomName: "outbound",
1043+
ParticipantIdentity: "siptest_outbound",
1044+
ParticipantName: "Outbound Call",
1045+
ParticipantMetadata: `{"test":true, "dir": "out"}`,
1046+
Headers: headers, // This is the key - passing the Route header
1047+
})
1048+
t.Logf("outbound call ID: %s", r.SipCallId)
1049+
1050+
// Wait a bit to see if any messages are received on the route target
1051+
select {
1052+
case msg := <-receivedMessages:
1053+
t.Logf("Received message on route target: %s", msg)
1054+
// If we receive a message, it means the Route header is working
1055+
require.Contains(t, msg, "INVITE", "Should receive INVITE message on route target")
1056+
t.Log("SUCCESS: Route header is working - message was sent to route target instead of request URI")
1057+
case <-time.After(10 * time.Second):
1058+
t.Fatal("No message received on route target within timeout - Route header processing is not working correctly")
1059+
}
1060+
}

0 commit comments

Comments
 (0)