Improve BizTalk endpoint address detection

This commit is contained in:
2026-08-06 18:20:38 +02:00
parent ac51714a88
commit 26e6ea5aac
6 changed files with 116 additions and 21 deletions
+6 -2
View File
@@ -249,8 +249,12 @@ Katalog stehen. Dynamische Send Ports, lokale Dateipfade, SMTP-Empfängerlisten
und Named Pipes sowie relative HTTP-/WCF-Receive-Listener werden als erwartbar und Named Pipes sowie relative HTTP-/WCF-Receive-Listener werden als erwartbar
nicht socket-prüfbar behandelt und erzeugen kein `UNKNOWN`. WCF-SQL-Adressen nicht socket-prüfbar behandelt und erzeugen kein `UNKNOWN`. WCF-SQL-Adressen
im Format `mssql://server[:port]/instanz/datenbank` werden auf TCP 1433 oder im Format `mssql://server[:port]/instanz/datenbank` werden auf TCP 1433 oder
den expliziten Port reduziert. Echte Auflösungslücken bleiben sichtbar und den expliziten Port reduziert. Schemafreie `host:port`-Angaben werden vor der
nennen Artefakt, Adapter und Grund, aber niemals die vollständige URI. allgemeinen URI-Auswertung erkannt. Bei FTP und SFTP werden zusätzlich Angaben
der Form `[benutzer@]host[:port]/pfad` auf Host und expliziten beziehungsweise
adaptertypischen Standardport reduziert. Echte Auflösungslücken bleiben
sichtbar und nennen Artefakt, Adapter und Grund, aber niemals die vollständige
URI.
Der lokale Katalog Der lokale Katalog
`%ProgramData%\BizTalkCheckmkPulse\data\endpoints.xml` wird beim ersten `%ProgramData%\BizTalkCheckmkPulse\data\endpoints.xml` wird beim ersten
+3
View File
@@ -119,6 +119,9 @@ Der Provider nutzt für die Endpoint-Discovery keine neue WMI-Klasse. Er liest
reiner Host/Port-Test: HTTP(S), SFTP, FTP, WCF/net.tcp, WCF-SQL/mssql und UNC reiner Host/Port-Test: HTTP(S), SFTP, FTP, WCF/net.tcp, WCF-SQL/mssql und UNC
werden per TCP geprüft; explizite `udp://`-Ziele per UDP-Datagramm. Es werden werden per TCP geprüft; explizite `udp://`-Ziele per UDP-Datagramm. Es werden
keine HTTP-Requests, Anmeldungen oder fachlichen Nachrichten gesendet. keine HTTP-Requests, Anmeldungen oder fachlichen Nachrichten gesendet.
Auch schemafreie `host:port`-Angaben sowie FTP-/SFTP-Adressen der Form
`[benutzer@]host[:port]/pfad` werden erkannt; Benutzer und Pfad werden dabei
nicht in den Endpoint-Katalog übernommen.
Nicht jede aktive BizTalk-Adresse ist selbst ein Socket-Ziel. Dynamische Send Nicht jede aktive BizTalk-Adresse ist selbst ein Socket-Ziel. Dynamische Send
Ports, lokale Pfade, SMTP-Empfänger, Named Pipes und relative HTTP-/WCF- Ports, lokale Pfade, SMTP-Empfänger, Named Pipes und relative HTTP-/WCF-
+2 -1
View File
@@ -183,7 +183,8 @@ Zweck:
- Reduziert die Adresse auf Host/Port und führt keinen fachlichen - Reduziert die Adresse auf Host/Port und führt keinen fachlichen
HTTP-, FTP-, SFTP-, WCF- oder BizTalk-Aufruf aus. HTTP-, FTP-, SFTP-, WCF- oder BizTalk-Aufruf aus.
- Erkennt unter anderem Standard-URIs, UNC, MSMQ, `net.tcp`, WCF-SQL- - Erkennt unter anderem Standard-URIs, UNC, MSMQ, `net.tcp`, WCF-SQL-
`mssql`-URIs und lokale HTTP-/WCF-Wildcard-Listener. `mssql`-URIs, schemafreie `host:port`-Angaben, FTP-/SFTP-Ziele mit optionalen
Benutzerinformationen sowie lokale HTTP-/WCF-Wildcard-Listener.
- Behandelt Adressen ohne eigenständiges Socket-Ziel (zum Beispiel lokale - Behandelt Adressen ohne eigenständiges Socket-Ziel (zum Beispiel lokale
Pfade, SMTP-Empfänger, Named Pipes und relative Receive-Listener) als Pfade, SMTP-Empfänger, Named Pipes und relative Receive-Listener) als
erwartbar ausgenommen und nicht als Abdeckungsfehler. erwartbar ausgenommen und nicht als Abdeckungsfehler.
+8
View File
@@ -65,6 +65,14 @@ geschrieben. Der Katalog liegt im bereits ACL-geschützten `data`-Verzeichnis;
nur Provider und Administratoren dürfen ihn ändern. `LocalSystem` liest nur nur Provider und Administratoren dürfen ihn ändern. `LocalSystem` liest nur
den separaten, validierten Checkmk-Snapshot. den separaten, validierten Checkmk-Snapshot.
Die automatische Auflösung akzeptiert neben absoluten URIs auch `host:port`
und bei FTP/SFTP die schemafreie Form `[benutzer@]host[:port]/pfad`. Die
Reihenfolge ist absichtlich eindeutig: `host:port` wird vor der allgemeinen
.NET-URI-Auswertung erkannt, damit der Host nicht fälschlich als URI-Schema
gedeutet wird. Benutzerinformationen und Pfad bleiben ausschließlich im
flüchtigen WMI-Kandidaten und werden nicht persistiert. Eckige URI-Klammern
um IPv6-Adressen werden vor der Socket-Probe entfernt.
## Manuelle Korrektur eines automatisch nicht auflösbaren Endpoints ## Manuelle Korrektur eines automatisch nicht auflösbaren Endpoints
Vor einer manuellen Änderung den Scheduled Task kurz stoppen und die Datei Vor einer manuellen Änderung den Scheduled Task kurz stoppen und die Datei
@@ -48,7 +48,16 @@ namespace BizTalkCheckmkPulse
return Unresolved("Aktive statische Transportadresse ist leer."); return Unresolved("Aktive statische Transportadresse ist leer.");
} }
if (LooksLikeEmailRecipients(address) || Contains(candidate.AdapterName, "SMTP")) if (Contains(candidate.AdapterName, "SMTP"))
{
return Expected("SMTP-Empfaenger beziehungsweise Adapteradresse enthaelt nicht den SMTP-Server.");
}
// SFTP-/FTP-Adressen duerfen Benutzerinformationen enthalten (user@host).
// Deshalb erst nach der adapterspezifischen Aufloesung als Empfaengerliste einstufen.
if (LooksLikeEmailRecipients(address)
&& !Contains(candidate.AdapterName, "SFTP")
&& !Contains(candidate.AdapterName, "FTP"))
{ {
return Expected("SMTP-Empfaenger beziehungsweise Adapteradresse enthaelt nicht den SMTP-Server."); return Expected("SMTP-Empfaenger beziehungsweise Adapteradresse enthaelt nicht den SMTP-Server.");
} }
@@ -144,7 +153,9 @@ namespace BizTalkCheckmkPulse
return true; return true;
} }
if (LooksLikeEmailRecipients(address)) if (LooksLikeEmailRecipients(address)
&& !Contains(adapterName, "SFTP")
&& !Contains(adapterName, "FTP"))
{ {
reason = "SMTP-Empfaengerliste ist kein pruefbarer Netzwerk-Endpunkt."; reason = "SMTP-Empfaengerliste ist kein pruefbarer Netzwerk-Endpunkt.";
return false; return false;
@@ -171,6 +182,28 @@ namespace BizTalkCheckmkPulse
return Validate(host, port, out reason); return Validate(host, port, out reason);
} }
// Host:Port muss vor Uri.TryCreate ausgewertet werden: .NET interpretiert
// beispielsweise "server:2222/path" sonst als URI mit dem Schema "server".
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);
}
// BizTalk-FTP-/SFTP-Konfigurationen kommen auch ohne Schema vor. Durch
// die temporaere URI-Normalisierung werden user@host, IPv6 und Ports
// sauber getrennt; Benutzername und Pfad gelangen nicht in den Katalog.
if (TryResolveAdapterUri(address, adapterName, out host, out port))
{
return Validate(host, port, out reason);
}
Uri uri; Uri uri;
if (Uri.TryCreate(address, UriKind.Absolute, out uri) if (Uri.TryCreate(address, UriKind.Absolute, out uri)
&& !string.IsNullOrWhiteSpace(uri.Scheme)) && !string.IsNullOrWhiteSpace(uri.Scheme))
@@ -184,7 +217,7 @@ namespace BizTalkCheckmkPulse
return false; return false;
} }
host = uri.Host; host = NormalizeHost(uri.Host);
port = 445; port = 445;
return Validate(host, port, out reason); return Validate(host, port, out reason);
} }
@@ -195,7 +228,7 @@ namespace BizTalkCheckmkPulse
return false; return false;
} }
host = uri.Host; host = NormalizeHost(uri.Host);
protocol = scheme == "udp" ? "UDP" : "TCP"; protocol = scheme == "udp" ? "UDP" : "TCP";
port = ResolvePort(uri, scheme); port = ResolvePort(uri, scheme);
if (port <= 0 && HasExplicitPort(address)) if (port <= 0 && HasExplicitPort(address))
@@ -212,18 +245,6 @@ namespace BizTalkCheckmkPulse
return Validate(host, port, out reason); 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")) if (Contains(adapterName, "SFTP"))
{ {
host = ExtractBareAdapterHost(address); host = ExtractBareAdapterHost(address);
@@ -318,6 +339,33 @@ namespace BizTalkCheckmkPulse
return separator < 0 ? value : value.Substring(0, separator); return separator < 0 ? value : value.Substring(0, separator);
} }
private static bool TryResolveAdapterUri(string address, string adapterName, out string host, out int port)
{
host = string.Empty;
port = 0;
var scheme = Contains(adapterName, "SFTP")
? "sftp"
: Contains(adapterName, "FTP") ? "ftp" : string.Empty;
if (scheme.Length == 0 || address.IndexOf("://", StringComparison.Ordinal) >= 0)
{
return false;
}
Uri uri;
var normalized = scheme + "://" + address.TrimStart('/');
if (!Uri.TryCreate(normalized, UriKind.Absolute, out uri)
|| string.IsNullOrWhiteSpace(uri.Host))
{
return false;
}
host = NormalizeHost(uri.Host);
port = uri.IsDefaultPort || uri.Port <= 0
? scheme == "sftp" ? 22 : 21
: uri.Port;
return true;
}
/// <summary> /// <summary>
/// Erkennt Adapter, deren relative Receive-Adresse eine lokale Listenerbindung statt eines Remoteziels beschreibt. /// Erkennt Adapter, deren relative Receive-Adresse eine lokale Listenerbindung statt eines Remoteziels beschreibt.
/// </summary> /// </summary>
@@ -385,10 +433,16 @@ namespace BizTalkCheckmkPulse
&& value.IndexOf(fragment, StringComparison.OrdinalIgnoreCase) >= 0; && value.IndexOf(fragment, StringComparison.OrdinalIgnoreCase) >= 0;
} }
private static string NormalizeHost(string host)
{
return (host ?? string.Empty).Trim().Trim('[', ']');
}
private static bool Validate(string host, int port, out string reason) private static bool Validate(string host, int port, out string reason)
{ {
host = (host ?? string.Empty).Trim(); host = NormalizeHost(host);
if (host.Length == 0 || host.IndexOfAny(new[] { ' ', '\t', '\r', '\n', '*', '%' }) >= 0) if (host.Length == 0
|| host.IndexOfAny(new[] { ' ', '\t', '\r', '\n', '*', '%', '/', '\\', '@', ';', '?', '#' }) >= 0)
{ {
reason = "Host ist leer oder enthaelt nicht aufgeloeste Platzhalter."; reason = "Host ist leer oder enthaelt nicht aufgeloeste Platzhalter.";
return false; return false;
@@ -405,6 +405,31 @@ namespace BizTalkCheckmkPulse.Tests
Assert(EndpointAddressParser.TryCreate(candidate, out endpoint, out reason), "net.tcp URI should resolve: " + reason); Assert(EndpointAddressParser.TryCreate(candidate, out endpoint, out reason), "net.tcp URI should resolve: " + reason);
AssertEqual(808, endpoint.Port, "net.tcp default port"); AssertEqual(808, endpoint.Port, "net.tcp default port");
candidate.Address = "transfer.example.test:2222/outbound";
candidate.AdapterName = "Custom Adapter";
Assert(EndpointAddressParser.TryCreate(candidate, out endpoint, out reason), "plain host:port should resolve before URI parsing: " + reason);
AssertEqual("transfer.example.test", endpoint.Host, "plain host:port host");
AssertEqual(2222, endpoint.Port, "plain host:port port");
candidate.Address = "integration-user@sftp.example.test/inbound";
candidate.AdapterName = "SFTP";
Assert(EndpointAddressParser.TryCreate(candidate, out endpoint, out reason), "schema-less SFTP URI with user info should resolve: " + reason);
AssertEqual("sftp.example.test", endpoint.Host, "schema-less SFTP host");
AssertEqual(22, endpoint.Port, "schema-less SFTP default port");
Assert(endpoint.Host.IndexOf("integration-user", StringComparison.OrdinalIgnoreCase) < 0, "SFTP user info must not enter catalog host");
candidate.Address = "ftp-user@ftp.example.test:2121/drop";
candidate.AdapterName = "FTP";
Assert(EndpointAddressParser.TryCreate(candidate, out endpoint, out reason), "schema-less FTP URI with user info should resolve: " + reason);
AssertEqual("ftp.example.test", endpoint.Host, "schema-less FTP host");
AssertEqual(2121, endpoint.Port, "schema-less FTP explicit port");
candidate.Address = "sftp://[2001:db8::10]:2222/inbound";
candidate.AdapterName = "SFTP";
Assert(EndpointAddressParser.TryCreate(candidate, out endpoint, out reason), "bracketed IPv6 URI should resolve: " + reason);
AssertEqual("2001:db8::10", endpoint.Host, "IPv6 catalog host must not retain URI brackets");
AssertEqual(2222, endpoint.Port, "IPv6 explicit port");
candidate.Address = "oracledb://oracle01/ORDERS/Dedicated"; candidate.Address = "oracledb://oracle01/ORDERS/Dedicated";
candidate.AdapterName = "WCF-OracleDB"; candidate.AdapterName = "WCF-OracleDB";
Assert(EndpointAddressParser.TryCreate(candidate, out endpoint, out reason), "direct Oracle DB URI should resolve: " + reason); Assert(EndpointAddressParser.TryCreate(candidate, out endpoint, out reason), "direct Oracle DB URI should resolve: " + reason);