|
5 | 5 | "fmt" |
6 | 6 | "log/slog" |
7 | 7 | "math/rand" |
| 8 | + "net" |
8 | 9 | "net/netip" |
9 | 10 | "os" |
10 | 11 | "strconv" |
@@ -945,3 +946,115 @@ func TestSIPOutbound(t *testing.T) { |
945 | 946 | }) |
946 | 947 | } |
947 | 948 | } |
| 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