fix(opcuaclient): keep the configured IPv6 zone index through the endpoint rewrite (#819)
.NET's Uri parses a zone ([fe80::1%eth0]) but drops it from Host, Authority, AbsoluteUri and ToString(); only DnsSafeHost/IdnHost carry it. The shared OpcUaEndpointUrlRewriter rebuilt the URL through UriBuilder, so the zone was lost and link-local endpoints were unroutable. The SDK dials new Uri(EndpointUrl).IdnHost (TcpByteTransport -> DnsEndPoint), which keeps it, so splicing the zone back into the string is enough. Kept verbatim (%eth0, %4, or RFC 6874 %25eth0) so the session dials exactly what GetEndpoints reached. Applies to the driver, the AdminUI browser and the Client CLI/UI (all share the rewriter).
This commit is contained in:
+28
-1
@@ -46,6 +46,33 @@ public static class OpcUaEndpointUrlRewriter
|
||||
return advertisedUrl;
|
||||
var builder = new UriBuilder(advertised) { Host = configured.DnsSafeHost, Port = port };
|
||||
// AbsoluteUri, not ToString(): ToString() un-escapes, turning an advertised "/x%20y" into "/x y" (#812).
|
||||
return builder.Uri.AbsoluteUri;
|
||||
return RestoreZoneIndex(builder.Uri, configured);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #819 — keep an IPv6 zone index (<c>[fe80::1%eth0]</c>) from the configured host. .NET's <see cref="Uri"/>
|
||||
/// parses the zone (only <see cref="Uri.DnsSafeHost"/> / <see cref="Uri.IdnHost"/> carry it) but drops it from
|
||||
/// <see cref="Uri.Host"/>, <see cref="Uri.Authority"/>, <see cref="Uri.AbsoluteUri"/> and <c>ToString()</c>, so
|
||||
/// any re-serialised URL loses it. The SDK dials <c>new Uri(EndpointUrl).IdnHost</c>, which DOES carry the zone
|
||||
/// — so the zone is spliced back into the string, verbatim as the operator wrote it (<c>%eth0</c>, <c>%4</c>, or
|
||||
/// an RFC 6874 <c>%25eth0</c>: not normalised, so the session dials exactly what GetEndpoints just reached).
|
||||
/// Without it a link-local endpoint is unroutable: the session would dial a zone-less <c>fe80::</c> address.
|
||||
/// </summary>
|
||||
/// <param name="rebuilt">The rewritten URL (configured host, zone already lost).</param>
|
||||
/// <param name="configured">The operator-configured URL.</param>
|
||||
/// <returns><paramref name="rebuilt"/>'s absolute URI with the configured zone index restored.</returns>
|
||||
private static string RestoreZoneIndex(Uri rebuilt, Uri configured)
|
||||
{
|
||||
var absolute = rebuilt.AbsoluteUri;
|
||||
if (configured.HostNameType != UriHostNameType.IPv6) return absolute;
|
||||
var zoned = configured.DnsSafeHost; // "fe80::1%eth0" — the only accessor that keeps it
|
||||
if (zoned.IndexOf('%') < 0) return absolute;
|
||||
|
||||
var bracketed = rebuilt.Host; // "[fe80::1]"
|
||||
var authorityStart = absolute.IndexOf("://", StringComparison.Ordinal);
|
||||
if (authorityStart < 0) return absolute;
|
||||
var hostAt = absolute.IndexOf(bracketed, authorityStart + 3, StringComparison.OrdinalIgnoreCase);
|
||||
if (hostAt < 0) return absolute;
|
||||
return absolute[..hostAt] + "[" + zoned + "]" + absolute[(hostAt + bracketed.Length)..];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user