From 8fd808e8f4d9f1a32a75f4c89cdd20cdd7861656 Mon Sep 17 00:00:00 2001 From: UbitUmarov Date: Thu, 26 Mar 2026 01:06:11 +0000 Subject: [PATCH] make sure we close parcel voice channels when user leaves region --- .../Janus/JanusViewerSession.cs | 2 + .../Janus/WebRtcJanusService.cs | 18 -- .../WebRtcVoice/IVoiceViewerSession.cs | 12 + .../WebRtcVoice/VoiceViewerSession.cs | 1 + .../WebRtcVoiceServiceConnector.cs | 2 +- .../WebRtcVoiceRegionModule.cs | 208 ++++++++++++------ .../WebRtcVoiceServiceModule.cs | 59 ++--- 7 files changed, 179 insertions(+), 123 deletions(-) diff --git a/OpenSim/Addons/os-webrtc-janus/Janus/JanusViewerSession.cs b/OpenSim/Addons/os-webrtc-janus/Janus/JanusViewerSession.cs index 97ddbcfeb4..7ab3e801c9 100644 --- a/OpenSim/Addons/os-webrtc-janus/Janus/JanusViewerSession.cs +++ b/OpenSim/Addons/os-webrtc-janus/Janus/JanusViewerSession.cs @@ -53,6 +53,8 @@ namespace osWebRtcVoice // IVoiceViewerSession.AgentId public OMV.UUID AgentId { get; set; } + public IVoiceViewerSession.VFlags Flags { get; set; } + // Janus keeps track of the user by this ID public long ParticipantId { get; set; } diff --git a/OpenSim/Addons/os-webrtc-janus/Janus/WebRtcJanusService.cs b/OpenSim/Addons/os-webrtc-janus/Janus/WebRtcJanusService.cs index a861f66700..eb74403d37 100644 --- a/OpenSim/Addons/os-webrtc-janus/Janus/WebRtcJanusService.cs +++ b/OpenSim/Addons/os-webrtc-janus/Janus/WebRtcJanusService.cs @@ -227,24 +227,6 @@ namespace osWebRtcVoice return $"flow={pFlowId}, viewer_session={pViewerSession?.ViewerSessionID ?? ""}"; } - private async Task EnforceRejoinCooldown(UUID pAgentId, JanusViewerSession pViewerSession, long pFlowId) - { - if (_RejoinCooldownMs <= 0) - return; - - if (_LastDisconnectByAgent.TryGetValue(pAgentId, out DateTime lastDisconnectUtc)) - { - int elapsedMs = (int)(DateTime.UtcNow - lastDisconnectUtc).TotalMilliseconds; - int waitMs = _RejoinCooldownMs - elapsedMs; - if (waitMs > 0) - { - _log.DebugFormat("{0} ProvisionVoiceAccountRequest: applying rejoin cooldown {1}ms ({2})", - LogHeader, waitMs, FlowTag(pFlowId, pViewerSession)); - await Task.Delay(waitMs); - } - } - } - // Disconnect the viewer session. This is called when the viewer logs out or hangs up. private void DisconnectViewerSession(JanusViewerSession pViewerSession, string pReason) diff --git a/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/IVoiceViewerSession.cs b/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/IVoiceViewerSession.cs index 92d61f8048..7126a2f543 100644 --- a/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/IVoiceViewerSession.cs +++ b/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/IVoiceViewerSession.cs @@ -25,6 +25,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +using System; using System.Threading.Tasks; using OMV = OpenMetaverse; @@ -37,6 +38,16 @@ namespace osWebRtcVoice /// public interface IVoiceViewerSession { + [Flags] + public enum VFlags: uint + { + None = 0, + IsParcel = 1, + IsEstate = IsParcel | 2, + IsAdmin = 4, + IsChildAgent = 8, + } + // This ID is passed to and from the viewer to identify the session public string ViewerSessionID { get; set; } public IWebRtcVoiceService VoiceService { get; set; } @@ -47,6 +58,7 @@ namespace osWebRtcVoice // The simulator has a GUID to identify the user public OMV.UUID AgentId { get; set; } + public VFlags Flags { get; set; } // Disconnect the connection to the voice service for this session public Task Shutdown(); diff --git a/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/VoiceViewerSession.cs b/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/VoiceViewerSession.cs index 72d40e485e..fe5604ec78 100644 --- a/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/VoiceViewerSession.cs +++ b/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/VoiceViewerSession.cs @@ -49,6 +49,7 @@ namespace osWebRtcVoice public string VoiceServiceSessionId { get; set; } public UUID RegionId { get; set; } public UUID AgentId { get; set; } + public IVoiceViewerSession.VFlags Flags { get; set; } // ===================================================================== // ViewerSessions hold the connection information for the client connection through to the voice service. diff --git a/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/WebRtcVoiceServiceConnector.cs b/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/WebRtcVoiceServiceConnector.cs index 3fa1b0d3be..e175db9871 100644 --- a/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/WebRtcVoiceServiceConnector.cs +++ b/OpenSim/Addons/os-webrtc-janus/WebRtcVoice/WebRtcVoiceServiceConnector.cs @@ -81,7 +81,7 @@ namespace osWebRtcVoice public IVoiceViewerSession CreateViewerSession(OSDMap pRequest, UUID pUserID, UUID pSceneID) { m_log.Debug($"{LogHeader} CreateViewerSession"); - return new VoiceViewerSession(this, pSceneID, pUserID); + return new VoiceViewerSession(this, pSceneID, pUserID); } public OSDMap ProvisionVoiceAccountRequest(OSDMap pRequest, UUID pUserID, UUID pSceneID) diff --git a/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceRegionModule/WebRtcVoiceRegionModule.cs b/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceRegionModule/WebRtcVoiceRegionModule.cs index 5ac1283787..c1d21a6d3d 100644 --- a/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceRegionModule/WebRtcVoiceRegionModule.cs +++ b/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceRegionModule/WebRtcVoiceRegionModule.cs @@ -166,6 +166,11 @@ namespace osWebRtcVoice OnRegisterCaps(scene, agentID, caps); }; + scene.EventManager.OnRemovePresence += delegate (UUID agentID) + { + OnRemovePresence(scene, agentID); + }; + ISimulatorFeaturesModule simFeatures = scene.RequestModuleInterface(); simFeatures?.AddFeature("VoiceServerType", OSD.FromString("webrtc")); } @@ -188,42 +193,52 @@ namespace osWebRtcVoice get { return null; } } - // ===================================================================== - // Thought about doing this but currently relying on the voice service - // event ("hangup") to remove the viewer session. -/* - private void Event_OnRemovePresence(UUID pAgentID) + private static void OnRemovePresence(Scene pScene, UUID pAgentID) { - // When a presence is removed, remove the viewer sessions for that agent - IEnumerable> vSessions; - if (VoiceViewerSession.TryGetViewerSessionByAgentId(pAgentID, out vSessions)) + // When a presence is removed, remove the parcel viewer sessions for that agent + List toremove = []; + if (VoiceViewerSession.TryGetViewerSessionsByAgentAndRegion(pAgentID, pScene.RegionInfo.RegionID, out IEnumerable> vSessions)) { foreach(KeyValuePair v in vSessions) { - m_log.DebugFormat("{0} Event_OnRemovePresence: removing viewer session {1}", LogHeader, v.Key); - VoiceViewerSession.RemoveViewerSession(v.Key); - v.Value.Shutdown(); + if((v.Value.Flags & IVoiceViewerSession.VFlags.IsParcel) != 0) + toremove.Add(v.Value); + } + + if(toremove.Count > 0) + { + foreach(IVoiceViewerSession v in toremove) + VoiceViewerSession.RemoveViewerSession(v.ViewerSessionID); + + Util.FireAndForget( x => + { + List toremoveas = toremove; + foreach(IVoiceViewerSession v in toremoveas) + try + { + OSDMap vreq = new() + { + { "logout" , true}, + { "viewer_session" , v.ViewerSessionID} + }; + v.VoiceService.ProvisionVoiceAccountRequest(v, vreq , v.AgentId, v.RegionId); + } + catch (Exception ex) + { + m_log.Debug( + $"{LogHeader} OnRemovePresence: shutdown failed for viewer_session {v.ViewerSessionID}: {ex.Message}"); + } + }); } } } -*/ - - private static List> GetViewerSessionsByAgentAndScene(UUID pAgentID, UUID pSceneID) - { - List> matches = []; - if (VoiceViewerSession.TryGetViewerSessionsByAgentId(pAgentID, out IEnumerable> vSessions)) - { - foreach (KeyValuePair v in vSessions) - matches.Add(v); - } - return matches; - } - + private static void CleanupDuplicateSessions(UUID pAgentID, UUID pSceneID, string pKeepViewerSessionId) { if(VoiceViewerSession.TryGetViewerSessionsByAgentAndRegion(pAgentID, pSceneID, out IEnumerable> candidates)) { bool noskip = string.IsNullOrEmpty(pKeepViewerSessionId); + List toremove = []; foreach (KeyValuePair candidate in candidates) { if (noskip && candidate.Key == pKeepViewerSessionId) @@ -231,25 +246,37 @@ namespace osWebRtcVoice m_log.Warn( $"{LogHeader} CleanupDuplicateSessions: removing stale viewer_session {candidate.Key} for agent {pAgentID}, scene {pSceneID}"); + toremove.Add(candidate.Value); + } + foreach(IVoiceViewerSession v in toremove) + VoiceViewerSession.RemoveViewerSession(v.ViewerSessionID); - VoiceViewerSession.RemoveViewerSession(candidate.Key); - _ = Task.Run(async () => + if(toremove.Count > 0) + { + Util.FireAndForget( x => { - try + foreach(IVoiceViewerSession v in toremove) { - await candidate.Value.Shutdown(); - } - catch (Exception ex) - { - m_log.Debug( - $"{LogHeader} CleanupDuplicateSessions: shutdown failed for viewer_session {candidate.Key}: {ex.Message}"); + try + { + OSDMap vreq = new() + { + { "logout" , true}, + { "viewer_session" , v.ViewerSessionID} + }; + v.VoiceService.ProvisionVoiceAccountRequest(v, vreq , v.AgentId, v.RegionId); + } + catch (Exception ex) + { + m_log.Debug( + $"{LogHeader} CleanupDuplicateSessions: shutdown failed for viewer_session {v.ViewerSessionID}: {ex.Message}"); + } } }); } } } - // // OnRegisterCaps is invoked via the scene.EventManager // everytime OpenSim hands out capabilities to a client @@ -357,7 +384,8 @@ namespace osWebRtcVoice IVoiceViewerSession v = kvp.Value; if(v is null) continue; - vreq["viewer_session"] = v.VoiceServiceSessionId; + vreq["viewer_session"] = v.ViewerSessionID; + VoiceViewerSession.RemoveViewerSession(v.ViewerSessionID); v.VoiceService.ProvisionVoiceAccountRequest(v, vreq , agentID, scene.RegionInfo.RegionID); } } @@ -367,18 +395,19 @@ namespace osWebRtcVoice return ; } + OSDMap logoutresp = null; if (VoiceViewerSession.TryGetViewerSession(viewerSessionId, out vSession)) { VoiceViewerSession.RemoveViewerSession(viewerSessionId); - OSDMap logoutresp = vSession.VoiceService.ProvisionVoiceAccountRequest(vSession, map, agentID, scene.RegionInfo.RegionID); - logoutresp ??= new OSDMap() { - { "response", "error" }, - { "message", "Logout session not found" } }; - - response.RawBuffer = OSDParser.SerializeLLSDXmlBytes(logoutresp); - response.StatusCode = (int)HttpStatusCode.OK; - return ; + logoutresp = vSession.VoiceService.ProvisionVoiceAccountRequest(vSession, map, agentID, scene.RegionInfo.RegionID); } + logoutresp ??= new OSDMap() { + { "response", "error" }, + { "message", "Logout session not found" } }; + + response.RawBuffer = OSDParser.SerializeLLSDXmlBytes(logoutresp); + response.StatusCode = (int)HttpStatusCode.OK; + return ; } // request has a viewer session. Use that to find the voice service @@ -387,7 +416,6 @@ namespace osWebRtcVoice CleanupDuplicateSessions(agentID, scene.RegionInfo.RegionID, viewerSessionId); } } - else { //no session id.. new channel? @@ -395,6 +423,16 @@ namespace osWebRtcVoice { CleanupDuplicateSessions(agentID, scene.RegionInfo.RegionID, null); + if(!scene.TryGetScenePresence(agentID, out ScenePresence sp)) + { + m_log.Debug($"{LogHeader}[ProvisionVoice]:avatar not found"); + response.RawBuffer = llsdUndefAnswerBytes; + response.StatusCode = (int)HttpStatusCode.NotFound; + return; + } + + IVoiceViewerSession.VFlags flags = IVoiceViewerSession.VFlags.None; + //do fully not trust viewers voice parcel requests if (channelType == "local") { @@ -413,14 +451,6 @@ namespace osWebRtcVoice return; } - if(!scene.TryGetScenePresence(agentID, out ScenePresence sp)) - { - m_log.Debug($"{LogHeader}[ProvisionVoice]:avatar not found"); - response.RawBuffer = llsdUndefAnswerBytes; - response.StatusCode = (int)HttpStatusCode.NotFound; - return; - } - if(map.TryGetInt("parcel_local_id", out int parcelID)) { ILandObject parcel = scene.LandChannel.GetLandObject(parcelID); @@ -430,7 +460,7 @@ namespace osWebRtcVoice response.StatusCode = (int)HttpStatusCode.NotFound; return; } - + LandData land = parcel.LandData; if (land == null) { @@ -453,25 +483,57 @@ namespace osWebRtcVoice // request and return the appropriate voice credentials for the estate channel // instead of a parcel channel map.Remove("parcel_local_id"); // estate channel + flags = IVoiceViewerSession.VFlags.IsEstate; } - else if(parcel.IsRestrictedFromLand(agentID) || parcel.IsBannedFromLand(agentID)) + else { - // check Z distance? - m_log.Debug($"{LogHeader}[ProvisionVoice]:agent not allowed on parcel"); - response.RawBuffer = llsdUndefAnswerBytes; - response.StatusCode = (int)HttpStatusCode.Forbidden; - return; + if(parcel.IsRestrictedFromLand(agentID) || parcel.IsBannedFromLand(agentID)) + { + // check Z distance? + m_log.Debug($"{LogHeader}[ProvisionVoice]:agent not allowed on parcel"); + response.RawBuffer = llsdUndefAnswerBytes; + response.StatusCode = (int)HttpStatusCode.Forbidden; + return; + } + flags = parcel.OwnerID.Equals(agentID) ? + IVoiceViewerSession.VFlags.IsAdmin | IVoiceViewerSession.VFlags.IsParcel : + IVoiceViewerSession.VFlags.IsParcel; } } + else + { + flags = IVoiceViewerSession.VFlags.IsEstate; + } + // TODO: check if this userId is making a new session (case that user is reconnecting) vSession = m_spatialVoiceService.CreateViewerSession(map, agentID, scene.RegionInfo.RegionID); - if(vSession != null) VoiceViewerSession.AddViewerSession(vSession); + if(vSession != null) + { + if(sp.IsChildAgent) + flags |= IVoiceViewerSession.VFlags.IsChildAgent; + else if(scene.Permissions.IsEstateManager(agentID)) + flags |= IVoiceViewerSession.VFlags.IsAdmin; + vSession.Flags = flags; + VoiceViewerSession.AddViewerSession(vSession); + } } else { - // TODO: check if this userId is making a new session (case that user is reconnecting) + if(sp.IsChildAgent) + { + // check Z distance? + m_log.Debug($"{LogHeader}[ProvisionVoice]:child agent request non local voice"); + response.RawBuffer = llsdUndefAnswerBytes; + response.StatusCode = (int)HttpStatusCode.Forbidden; + return; + } + vSession = m_nonSpatialVoiceService.CreateViewerSession(map, agentID, scene.RegionInfo.RegionID); - if(vSession != null) VoiceViewerSession.AddViewerSession(vSession); + if(vSession != null) + { + vSession.Flags = IVoiceViewerSession.VFlags.IsAdmin; + VoiceViewerSession.AddViewerSession(vSession); + } } } } @@ -496,7 +558,6 @@ namespace osWebRtcVoice return; } - public void VoiceSignalingRequest(IOSHttpRequest request, IOSHttpResponse response, UUID agentID, Scene scene) { if(request.HttpMethod != "POST") @@ -602,6 +663,23 @@ namespace osWebRtcVoice return; } + string servertype = null; + if(reqmap.TryGetOSDMap("alt_params", out OSDMap altparams)) + { + if(!altparams.TryGetString("voice_server_type", out servertype)) + _ = altparams.TryGetString("preferred_voice_server_type", out servertype); + } + + if(!string.IsNullOrEmpty(servertype)) + { + if(!servertype.Equals("webrtc", StringComparison.OrdinalIgnoreCase)) + { + response.RawBuffer = llsdUndefAnswerBytes; + response.StatusCode = (int)HttpStatusCode.OK; + return; + } + } + switch (method.ToLower()) { // Several different method requests that we don't know how to handle. @@ -643,6 +721,10 @@ namespace osWebRtcVoice response.StatusCode = (int)HttpStatusCode.OK; } break; + case "call": + m_log.Debug($"{LogHeader}: ChatSessionRequest call: {reqmap}"); + response.StatusCode = (int)HttpStatusCode.BadRequest; + break; default: response.StatusCode = (int)HttpStatusCode.BadRequest; break; diff --git a/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceServiceModule/WebRtcVoiceServiceModule.cs b/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceServiceModule/WebRtcVoiceServiceModule.cs index 1fe410d9cd..f90894668c 100644 --- a/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceServiceModule/WebRtcVoiceServiceModule.cs +++ b/OpenSim/Addons/os-webrtc-janus/WebRtcVoiceServiceModule/WebRtcVoiceServiceModule.cs @@ -133,30 +133,7 @@ namespace osWebRtcVoice } - private static List> GetViewerSessionsByAgentAndScene(UUID pAgentID, UUID pSceneID) - { - List> matches = []; - if (VoiceViewerSession.TryGetViewerSessionsByAgentId(pAgentID, out IEnumerable> vSessions)) - { - foreach (KeyValuePair v in vSessions) - matches.Add(v); - } - return matches; - } - - private static object TryGetPropertyValue(object pSource, string pPropertyName) - { - if (pSource is null || string.IsNullOrEmpty(pPropertyName)) - return null; - - PropertyInfo propertyInfo = pSource.GetType().GetProperty(pPropertyName); - if (propertyInfo is null) - return null; - - return propertyInfo.GetValue(pSource); - } - - private static void CleanupDuplicateSessions(UUID pAgentID, UUID pSceneID, string pKeepViewerSessionId) + private static void CleanupDuplicateSessions(UUID pAgentID, UUID pSceneID, string pKeepViewerSessionId) { if(VoiceViewerSession.TryGetViewerSessionsByAgentAndRegion(pAgentID, pSceneID, out IEnumerable> candidates)) { @@ -211,32 +188,25 @@ namespace osWebRtcVoice IVoiceViewerSession v = kvp.Value; if(v is null) continue; - vreq["viewer_session"] = v.VoiceServiceSessionId; + vreq["viewer_session"] = v.ViewerSessionID; + + VoiceViewerSession.RemoveViewerSession(v.ViewerSessionID); v.VoiceService.ProvisionVoiceAccountRequest(v, vreq , pUserID, pSceneID); } - //return new OSDMap {{ "response", "closed" }}; } - /* - else - { - return new OSDMap - { - { "response", "error" }, - { "message", "Unable to provision voice session not found)" } - }; - } - */ + return new OSDMap {{ "response", "closed" }}; } + OSDMap resp = null; if (VoiceViewerSession.TryGetViewerSession(viewerSessionId, out vSession)) { VoiceViewerSession.RemoveViewerSession(viewerSessionId); - OSDMap resp = vSession.VoiceService.ProvisionVoiceAccountRequest(vSession, pRequest, pUserID, pSceneID); - return resp ?? new OSDMap() { - { "response", "error" }, - { "message", "Logout session not found" } }; + resp = vSession.VoiceService.ProvisionVoiceAccountRequest(vSession, pRequest, pUserID, pSceneID); } + return resp ?? new OSDMap() { + { "response", "error" }, + { "message", "Logout session not found" } }; } // request has a viewer session. Use that to find the voice service @@ -256,7 +226,14 @@ namespace osWebRtcVoice { // TODO: check if this userId is making a new session (case that user is reconnecting) vSession = m_spatialVoiceService.CreateViewerSession(pRequest, pUserID, pSceneID); - if(vSession != null) VoiceViewerSession.AddViewerSession(vSession); + if(vSession != null) + { + if(pRequest.TryGetInt("parcel_local_id", out int parcelID)) + vSession.Flags = IVoiceViewerSession.VFlags.IsParcel; + else + vSession.Flags = IVoiceViewerSession.VFlags.IsEstate; + VoiceViewerSession.AddViewerSession(vSession); + } } else {