Add BizTalk endpoint reachability monitoring

This commit is contained in:
2026-08-04 09:48:00 +02:00
parent 826d87fef7
commit 91e0db5619
20 changed files with 1714 additions and 38 deletions
@@ -0,0 +1,310 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
namespace BizTalkCheckmkPulse
{
/// <summary>
/// Reduziert adapter-spezifische BizTalk-Adressen auf einen geheimnisfreien Host/Port-Test.
/// </summary>
internal static class EndpointAddressParser
{
private static readonly Regex HostPortPattern = new Regex(
@"^(?<host>\[[0-9a-fA-F:]+\]|[a-zA-Z0-9._-]+):(?<port>\d{1,5})(?:[/\\].*)?$",
RegexOptions.CultureInvariant);
/// <summary>
/// Erstellt aus einer aktiven BizTalk-Transportadresse einen TCP-/UDP-Katalogeintrag.
/// </summary>
public static bool TryCreate(
EndpointCandidate candidate,
out EndpointCatalogEntry entry,
out string reason)
{
entry = null;
reason = string.Empty;
if (candidate == null)
{
reason = "Endpoint-Kandidat fehlt.";
return false;
}
if (candidate.Dynamic)
{
reason = "Dynamischer Send Port besitzt kein statisch pruefbares Ziel.";
return false;
}
var address = (candidate.Address ?? string.Empty).Trim();
if (address.Length == 0)
{
reason = "Transportadresse ist leer.";
return false;
}
string protocol;
string host;
int port;
if (!TryResolve(address, candidate.AdapterName, out protocol, out host, out port, out reason))
{
return false;
}
entry = new EndpointCatalogEntry
{
Key = candidate.Key,
ArtifactType = candidate.ArtifactType,
ApplicationName = candidate.ApplicationName,
ArtifactName = candidate.ArtifactName,
TransportRole = candidate.TransportRole,
AdapterName = candidate.AdapterName,
Protocol = protocol,
Host = host,
Port = port,
Enabled = true,
AutoDiscovered = true
};
return true;
}
/// <summary>
/// Unterscheidet externe, aber nicht automatisch aufloesbare Ziele von bewusst lokalen/variablen Adressen.
/// </summary>
public static bool IsPotentialExternalEndpoint(EndpointCandidate candidate)
{
if (candidate == null || candidate.Dynamic)
{
return false;
}
var address = (candidate.Address ?? string.Empty).Trim();
if (address.Length == 0
|| LooksLikeEmailRecipients(address)
|| Regex.IsMatch(address, @"^[a-zA-Z]:[\\/]", RegexOptions.CultureInvariant)
|| address.StartsWith("/", StringComparison.Ordinal)
|| address.StartsWith("net.pipe:", StringComparison.OrdinalIgnoreCase)
|| address.StartsWith("npipe:", StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (Contains(candidate.AdapterName, "SMTP")
|| Contains(candidate.AdapterName, "FILE") && !address.StartsWith("\\\\", StringComparison.Ordinal))
{
return false;
}
if (address.StartsWith("\\\\", StringComparison.Ordinal)
|| HostPortPattern.IsMatch(address)
|| Regex.IsMatch(address, @"(?:FORMATNAME:)?DIRECT=(?:OS|TCP):", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
{
return true;
}
Uri uri;
if (Uri.TryCreate(address, UriKind.Absolute, out uri))
{
return !string.Equals(uri.Scheme, "file", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(uri.Scheme, "mailto", StringComparison.OrdinalIgnoreCase);
}
return Contains(candidate.AdapterName, "SFTP")
|| Contains(candidate.AdapterName, "FTP")
|| Contains(candidate.AdapterName, "HTTP")
|| Contains(candidate.AdapterName, "SOAP")
|| Contains(candidate.AdapterName, "WCF");
}
private static bool TryResolve(
string address,
string adapterName,
out string protocol,
out string host,
out int port,
out string reason)
{
protocol = "TCP";
host = string.Empty;
port = 0;
reason = string.Empty;
if (LooksLikeEmailRecipients(address))
{
reason = "SMTP-Empfaengerliste ist kein pruefbarer Netzwerk-Endpunkt.";
return false;
}
if (address.StartsWith("\\\\", StringComparison.Ordinal))
{
var end = address.IndexOf('\\', 2);
host = end < 0 ? address.Substring(2) : address.Substring(2, end - 2);
port = 445;
return Validate(host, port, out reason);
}
if (Regex.IsMatch(address, @"^[a-zA-Z]:[\\/]", RegexOptions.CultureInvariant)
|| Path.IsPathRooted(address) && !address.StartsWith("/", StringComparison.Ordinal))
{
reason = "Lokaler Dateipfad benoetigt keine externe Netzwerkprobe.";
return false;
}
if (TryResolveMsmq(address, out host))
{
port = 1801;
return Validate(host, port, out reason);
}
Uri uri;
if (Uri.TryCreate(address, UriKind.Absolute, out uri)
&& !string.IsNullOrWhiteSpace(uri.Scheme))
{
var scheme = uri.Scheme.ToLowerInvariant();
if (scheme == "file")
{
if (string.IsNullOrWhiteSpace(uri.Host))
{
reason = "Lokaler file-Endpunkt benoetigt keine externe Netzwerkprobe.";
return false;
}
host = uri.Host;
port = 445;
return Validate(host, port, out reason);
}
if (scheme == "net.pipe" || scheme == "npipe")
{
reason = "Named Pipes sind keine TCP-/UDP-Endpunkte.";
return false;
}
host = uri.Host;
protocol = scheme == "udp" ? "UDP" : "TCP";
port = ResolvePort(uri, scheme);
if (port <= 0 && HasExplicitPort(address))
{
port = uri.Port;
}
if (port <= 0)
{
reason = "Fuer das Schema '" + scheme + "' konnte kein TCP-/UDP-Port bestimmt werden.";
return false;
}
return Validate(host, port, out reason);
}
var match = HostPortPattern.Match(address);
if (match.Success)
{
host = match.Groups["host"].Value.Trim('[', ']');
if (!int.TryParse(match.Groups["port"].Value, out port))
{
port = 0;
}
return Validate(host, port, out reason);
}
if (Contains(adapterName, "SFTP"))
{
host = address.Trim('/');
port = 22;
return Validate(host, port, out reason);
}
if (Contains(adapterName, "FTP"))
{
host = address.Trim('/');
port = 21;
return Validate(host, port, out reason);
}
reason = "Transportadresse enthaelt keinen sicher bestimmbaren Host und Port.";
return false;
}
private static int ResolvePort(Uri uri, string scheme)
{
if (uri.Port > 0 && !uri.IsDefaultPort)
{
return uri.Port;
}
switch (scheme)
{
case "http": return 80;
case "https": return 443;
case "ftp": return 21;
case "sftp": return 22;
case "smb": return 445;
case "smtp": return 25;
case "ldap": return 389;
case "ldaps": return 636;
case "pop3": return 110;
case "pop3s": return 995;
case "imap": return 143;
case "imaps": return 993;
case "msmq": return 1801;
default: return uri.Port > 0 ? uri.Port : 0;
}
}
private static bool HasExplicitPort(string address)
{
var authorityEnd = address.IndexOfAny(new[] { '/', '?' }, address.IndexOf("://", StringComparison.Ordinal) + 3);
var authority = authorityEnd < 0 ? address : address.Substring(0, authorityEnd);
return Regex.IsMatch(authority, @":\d{1,5}$", RegexOptions.CultureInvariant);
}
private static bool TryResolveMsmq(string address, out string host)
{
host = string.Empty;
var match = Regex.Match(
address,
@"(?:FORMATNAME:)?DIRECT=(?:OS|TCP):(?<host>[^\\/;]+)",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!match.Success)
{
return false;
}
host = match.Groups["host"].Value.Trim();
return host.Length > 0;
}
private static bool LooksLikeEmailRecipients(string value)
{
return value.IndexOf('@') > 0
&& value.IndexOf("://", StringComparison.Ordinal) < 0
&& !value.StartsWith("\\\\", StringComparison.Ordinal);
}
private static bool Contains(string value, string fragment)
{
return !string.IsNullOrWhiteSpace(value)
&& value.IndexOf(fragment, StringComparison.OrdinalIgnoreCase) >= 0;
}
private static bool Validate(string host, int port, out string reason)
{
host = (host ?? string.Empty).Trim();
if (host.Length == 0 || host.IndexOfAny(new[] { ' ', '\t', '\r', '\n', '*', '%' }) >= 0)
{
reason = "Host ist leer oder enthaelt nicht aufgeloeste Platzhalter.";
return false;
}
if (port < 1 || port > 65535)
{
reason = "Port liegt ausserhalb des gueltigen Bereichs.";
return false;
}
reason = string.Empty;
return true;
}
}
}