feat: harden gateway reply remap and leaf loop transparency

This commit is contained in:
Joseph Doherty
2026-02-23 14:40:07 -05:00
parent d83b37fec1
commit 958c4aa8ed
5 changed files with 70 additions and 10 deletions

View File

@@ -4,6 +4,10 @@ public static class ReplyMapper
{
private const string GatewayReplyPrefix = "_GR_.";
public static bool HasGatewayReplyPrefix(string? subject)
=> !string.IsNullOrWhiteSpace(subject)
&& subject.StartsWith(GatewayReplyPrefix, StringComparison.Ordinal);
public static string? ToGatewayReply(string? replyTo, string localClusterId)
{
if (string.IsNullOrWhiteSpace(replyTo))
@@ -16,14 +20,20 @@ public static class ReplyMapper
{
restoredReply = string.Empty;
if (string.IsNullOrWhiteSpace(gatewayReply) || !gatewayReply.StartsWith(GatewayReplyPrefix, StringComparison.Ordinal))
if (!HasGatewayReplyPrefix(gatewayReply))
return false;
var clusterSeparator = gatewayReply.IndexOf('.', GatewayReplyPrefix.Length);
if (clusterSeparator < 0 || clusterSeparator == gatewayReply.Length - 1)
return false;
var current = gatewayReply!;
while (HasGatewayReplyPrefix(current))
{
var clusterSeparator = current.IndexOf('.', GatewayReplyPrefix.Length);
if (clusterSeparator < 0 || clusterSeparator == current.Length - 1)
return false;
restoredReply = gatewayReply[(clusterSeparator + 1)..];
current = current[(clusterSeparator + 1)..];
}
restoredReply = current;
return true;
}
}

View File

@@ -4,6 +4,9 @@ public static class LeafLoopDetector
{
private const string LeafLoopPrefix = "$LDS.";
public static bool HasLoopMarker(string subject)
=> subject.StartsWith(LeafLoopPrefix, StringComparison.Ordinal);
public static string Mark(string subject, string serverId)
=> $"{LeafLoopPrefix}{serverId}.{subject}";
@@ -13,14 +16,20 @@ public static class LeafLoopDetector
public static bool TryUnmark(string subject, out string unmarked)
{
unmarked = subject;
if (!subject.StartsWith(LeafLoopPrefix, StringComparison.Ordinal))
if (!HasLoopMarker(subject))
return false;
var serverSeparator = subject.IndexOf('.', LeafLoopPrefix.Length);
if (serverSeparator < 0 || serverSeparator == subject.Length - 1)
return false;
var current = subject;
while (HasLoopMarker(current))
{
var serverSeparator = current.IndexOf('.', LeafLoopPrefix.Length);
if (serverSeparator < 0 || serverSeparator == current.Length - 1)
return false;
unmarked = subject[(serverSeparator + 1)..];
current = current[(serverSeparator + 1)..];
}
unmarked = current;
return true;
}
}

View File

@@ -879,6 +879,8 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
var replyTo = message.ReplyTo;
if (ReplyMapper.TryRestoreGatewayReply(replyTo, out var restoredReply))
replyTo = restoredReply;
else if (ReplyMapper.HasGatewayReplyPrefix(replyTo))
replyTo = null;
DeliverRemoteMessage(message.Account, message.Subject, replyTo, message.Payload);
}
@@ -891,6 +893,8 @@ public sealed class NatsServer : IMessageRouter, ISubListAccess, IDisposable
var subject = message.Subject;
if (LeafLoopDetector.TryUnmark(subject, out var unmarked))
subject = unmarked;
else if (LeafLoopDetector.HasLoopMarker(subject))
return;
DeliverRemoteMessage(message.Account, subject, message.ReplyTo, message.Payload);
}