diff --git a/api_signaling.go b/api_signaling.go index edd1fd2..6cfdec5 100644 --- a/api_signaling.go +++ b/api_signaling.go @@ -793,7 +793,8 @@ func (m *InternalClientMessage) CheckValid() error { } type InternalServerDialoutRequest struct { - RoomId string `json:"roomid"` + RoomId string `json:"roomid"` + Backend string `json:"backend"` Request *BackendRoomDialoutRequest `json:"request"` } diff --git a/backend_server.go b/backend_server.go index 1705f8e..78adc98 100644 --- a/backend_server.go +++ b/backend_server.go @@ -676,7 +676,7 @@ func isNumeric(s string) bool { return checkNumeric.MatchString(s) } -func (b *BackendServer) startDialout(roomid string, backend *Backend, request *BackendServerRoomRequest) (any, error) { +func (b *BackendServer) startDialout(roomid string, backend *Backend, backendUrl string, request *BackendServerRoomRequest) (any, error) { if err := request.Dialout.ValidateNumber(); err != nil { return returnDialoutError(http.StatusBadRequest, err) } @@ -685,17 +685,19 @@ func (b *BackendServer) startDialout(roomid string, backend *Backend, request *B return returnDialoutError(http.StatusBadRequest, NewError("invalid_roomid", "The room id must be numeric.")) } - var session *ClientSession - for s := range b.hub.dialoutSessions { - if s.GetClient() != nil { - session = s - break - } - } + session := b.hub.GetDialoutSession(roomid, backend) if session == nil { return returnDialoutError(http.StatusNotFound, NewError("no_client_available", "No available client found to trigger dialout.")) } + url := backend.Url() + if url == "" { + // Old-style compat backend, use client-provided URL. + url = backendUrl + if url != "" && url[len(url)-1] != '/' { + url += "/" + } + } id := newRandomString(32) msg := &ServerMessage{ Id: id, @@ -704,6 +706,7 @@ func (b *BackendServer) startDialout(roomid string, backend *Backend, request *B Type: "dialout", Dialout: &InternalServerDialoutRequest{ RoomId: roomid, + Backend: url, Request: request.Dialout, }, }, @@ -842,7 +845,7 @@ func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body case "switchto": err = b.sendRoomSwitchTo(roomid, backend, &request) case "dialout": - response, err = b.startDialout(roomid, backend, &request) + response, err = b.startDialout(roomid, backend, backendUrl, &request) default: http.Error(w, "Unsupported request type: "+request.Type, http.StatusBadRequest) return diff --git a/backend_server_test.go b/backend_server_test.go index a950b5c..1e14008 100644 --- a/backend_server_test.go +++ b/backend_server_test.go @@ -79,11 +79,18 @@ func CreateBackendServerForTestFromConfig(t *testing.T, config *goconf.ConfigFil if err != nil { t.Fatal(err) } - config.AddOption("backend", "allowed", u.Host) + if strings.Contains(t.Name(), "Compat") { + config.AddOption("backend", "allowed", u.Host) + config.AddOption("backend", "secret", string(testBackendSecret)) + } else { + backendId := "backend1" + config.AddOption("backend", "backends", backendId) + config.AddOption(backendId, "url", server.URL) + config.AddOption(backendId, "secret", string(testBackendSecret)) + } if u.Scheme == "http" { config.AddOption("backend", "allowhttp", "true") } - config.AddOption("backend", "secret", string(testBackendSecret)) config.AddOption("sessions", "hashkey", "12345678901234567890123456789012") config.AddOption("sessions", "blockkey", "09876543210987654321098765432109") config.AddOption("clients", "internalsecret", string(testInternalSecret)) @@ -226,8 +233,8 @@ func CreateBackendServerWithClusteringForTestFromConfig(t *testing.T, config1 *g return b1, b2, hub1, hub2, server1, server2 } -func performBackendRequest(url string, body []byte) (*http.Response, error) { - request, err := http.NewRequest("POST", url, bytes.NewReader(body)) +func performBackendRequest(requestUrl string, body []byte) (*http.Response, error) { + request, err := http.NewRequest("POST", requestUrl, bytes.NewReader(body)) if err != nil { return nil, err } @@ -236,7 +243,11 @@ func performBackendRequest(url string, body []byte) (*http.Response, error) { check := CalculateBackendChecksum(rnd, body, testBackendSecret) request.Header.Set("Spreed-Signaling-Random", rnd) request.Header.Set("Spreed-Signaling-Checksum", check) - request.Header.Set("Spreed-Signaling-Backend", url) + u, err := url.Parse(requestUrl) + if err != nil { + return nil, err + } + request.Header.Set("Spreed-Signaling-Backend", u.Scheme+"://"+u.Host) client := &http.Client{} return client.Do(request) } @@ -1886,6 +1897,115 @@ func TestBackendServer_DialoutAccepted(t *testing.T) { if msg.Internal.Dialout.RoomId != roomId { t.Errorf("expected room id %s, got %+v", roomId, msg) } + if url := server.URL + "/"; msg.Internal.Dialout.Backend != url { + t.Errorf("expected backend %s, got %+v", url, msg) + } + + response := &ClientMessage{ + Id: msg.Id, + Type: "internal", + Internal: &InternalClientMessage{ + Type: "dialout", + Dialout: &DialoutInternalClientMessage{ + Type: "status", + RoomId: msg.Internal.Dialout.RoomId, + Status: &DialoutStatusInternalClientMessage{ + Status: "accepted", + CallId: callId, + }, + }, + }, + } + if err := client.WriteJSON(response); err != nil { + t.Error(err) + } + }() + + defer func() { + <-stopped + }() + + msg := &BackendServerRoomRequest{ + Type: "dialout", + Dialout: &BackendRoomDialoutRequest{ + Number: "+1234567890", + }, + } + + data, err := json.Marshal(msg) + if err != nil { + t.Fatal(err) + } + res, err := performBackendRequest(server.URL+"/api/v1/room/"+roomId, data) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + t.Error(err) + } + if res.StatusCode != http.StatusOK { + t.Fatalf("Expected error %d, got %s: %s", http.StatusOK, res.Status, string(body)) + } + + var response BackendServerRoomResponse + if err := json.Unmarshal(body, &response); err != nil { + t.Fatal(err) + } + + if response.Type != "dialout" || response.Dialout == nil { + t.Fatalf("expected type dialout, got %s", string(body)) + } + if response.Dialout.Error != nil { + t.Fatalf("expected dialout success, got %s", string(body)) + } + if response.Dialout.CallId != callId { + t.Errorf("expected call id %s, got %s", callId, string(body)) + } +} + +func TestBackendServer_DialoutAcceptedCompat(t *testing.T) { + _, _, _, hub, _, server := CreateBackendServerForTest(t) + + client := NewTestClient(t, server, hub) + defer client.CloseWithBye() + if err := client.SendHelloInternalWithFeatures([]string{"start-dialout"}); err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + + _, err := client.RunUntilHello(ctx) + if err != nil { + t.Fatal(err) + } + + roomId := "12345" + callId := "call-123" + + stopped := make(chan struct{}) + go func() { + defer close(stopped) + + msg, err := client.RunUntilMessage(ctx) + if err != nil { + t.Error(err) + return + } + + if msg.Type != "internal" || msg.Internal.Type != "dialout" { + t.Errorf("expected internal dialout message, got %+v", msg) + return + } + + if msg.Internal.Dialout.RoomId != roomId { + t.Errorf("expected room id %s, got %+v", roomId, msg) + } + if url := server.URL + "/"; msg.Internal.Dialout.Backend != url { + t.Errorf("expected backend %s, got %+v", url, msg) + } response := &ClientMessage{ Id: msg.Id, @@ -1990,6 +2110,9 @@ func TestBackendServer_DialoutRejected(t *testing.T) { if msg.Internal.Dialout.RoomId != roomId { t.Errorf("expected room id %s, got %+v", roomId, msg) } + if url := server.URL + "/"; msg.Internal.Dialout.Backend != url { + t.Errorf("expected backend %s, got %+v", url, msg) + } response := &ClientMessage{ Id: msg.Id, diff --git a/hub.go b/hub.go index f57687b..d6c3086 100644 --- a/hub.go +++ b/hub.go @@ -581,6 +581,24 @@ func (h *Hub) GetSessionByPublicId(sessionId string) Session { return session } +func (h *Hub) GetDialoutSession(roomId string, backend *Backend) *ClientSession { + url := backend.Url() + + h.mu.RLock() + defer h.mu.RUnlock() + for session := range h.dialoutSessions { + if session.backend.Url() != url { + continue + } + + if session.GetClient() != nil { + return session + } + } + + return nil +} + func (h *Hub) checkExpiredSessions(now time.Time) { for s := range h.expiredSessions { if s.IsExpired(now) {