diff --git a/src/StackExchange.Redis/Format.cs b/src/StackExchange.Redis/Format.cs index 70cf7dbc0..70d8003aa 100644 --- a/src/StackExchange.Redis/Format.cs +++ b/src/StackExchange.Redis/Format.cs @@ -321,7 +321,18 @@ internal static bool TryParseEndPoint(string? addressWithPort, [NotNullWhen(true } #if UNIX_SOCKET - endpoint = new UnixDomainSocketEndPoint(addressWithPort.Substring(1)); + var path = addressWithPort.Substring(1); + if (path[0] == '@' && OperatingSystem.IsLinux()) + { + // "!@name" is the Linux ABSTRACT namespace, using the same '@' convention as + // socat/systemd (and redis-cli/redis-benchmark where supported): the kernel + // spelling is a leading NUL, which cannot appear in a config string. Linux-gated + // because no other platform has the namespace — elsewhere '@' stays a literal + // (strange) filename, matching what the other tools do. Note ToString() round-trips + // for free: UnixDomainSocketEndPoint renders abstract names back as "@name". + path = "\0" + path.Substring(1); + } + endpoint = new UnixDomainSocketEndPoint(path); return true; #else throw new PlatformNotSupportedException("Unix domain sockets require .NET Core 3 or above"); diff --git a/tests/StackExchange.Redis.Tests/FormatTests.cs b/tests/StackExchange.Redis.Tests/FormatTests.cs index 0054ce11d..1e41dcecf 100644 --- a/tests/StackExchange.Redis.Tests/FormatTests.cs +++ b/tests/StackExchange.Redis.Tests/FormatTests.cs @@ -50,6 +50,33 @@ public void ParseEndPoint(string data, EndPoint expected, string? expectedFormat Assert.Equal(expectedFormat, s); } + // UDS endpoints live outside EndpointData because UnixDomainSocketEndPoint does not implement + // value equality — these compare via ToString instead. +#if NET + [Fact] + public void ParseUnixDomainSocketEndPoint() + { + Assert.True(Format.TryParseEndPoint("!/tmp/redis.sock", out var ep)); + var uds = Assert.IsType(ep); + Assert.Equal("/tmp/redis.sock", uds.ToString()); + Assert.Equal("!/tmp/redis.sock", Format.ToString(ep)); + } + + [Fact] + public void ParseAbstractUnixDomainSocketEndPoint() + { + Assert.SkipUnless(OperatingSystem.IsLinux(), "the abstract socket namespace is Linux-only"); + + // "!@name": socat/systemd '@' convention for the Linux abstract namespace. The parse maps it + // to the kernel's leading-NUL spelling; UnixDomainSocketEndPoint.ToString renders that back + // as '@name', so the config string round-trips exactly. + Assert.True(Format.TryParseEndPoint("!@redis-abstract", out var ep)); + var uds = Assert.IsType(ep); + Assert.Equal("@redis-abstract", uds.ToString()); + Assert.Equal("!@redis-abstract", Format.ToString(ep)); + } +#endif + [Theory] [InlineData(CommandFlags.None, "None")] #if NETFRAMEWORK