End2End-LDAP

Jeder Windows Domain Controller stellt einen LDAP-Service unter 386/TCP, 636/TLS oder als Global-Catalog unter 3268/TCP bzw. 3269/TLS bereit. Über diese Ports replizieren sich die DCs untereinander aber auch Clients fragen hier hier. Und Clients sind keineswegs nur Windows PCs in der Domäne. Die sind in der Regel sogar eher zu vernachlässigen. Viele andere Dienste, z.B. Webseiten, Telefonanlagen, Mailrelays, Scanner etc. fragen so Benutzerinformationen ab und VPN-Server, Proxy-Server, Firewalls, WebServer o.ä. nutzen manchmal gerne die LDAP-Anmeldung als Hilfsmittel um die Gültigkeit von Anmeldedaten des Benutzer zu überprüfen. Insofern ist es schon wichtig zu sehen, wie gut die "Reaktionszeit" ist.

Problemstellung

Windows selbst hat einige Performancecounter, mit der die LDAP-Dienste eines Domaincontrollers numerisch erfasst werden können.

Auch eine vorbereitete Collection gibt es, die in dem Beispiel 5 Minuten lang die Performance misst und über die Report-Funktion später eine erste grobe Auswertung erlaubt. Aber keiner der Counter misst wirklich die Zeit, die vergeht wenn ein Client sich verbindet, eine Abfrage stellt und die Verbindung wieder schließt.

Genau dies ist aber erforderlich, wenn es sporadische oder nicht näher erklärbare Zugriffsprobleme gibt. Die wenigsten Anwendungen, die LDAP-Anfragen absetzen, haben ein ausgereiftes Logging und Serverprodukte wie Exchange und Lync nutzen eh einen lokalen Cache, so dass temporäre Störungen im LDAP-Service selten zu merklichen Probleme führen. Einfachere Programme, die aber auf einen LDAP-Server angewiesen sind, reagieren auf solche Verzögerungen empfindlich.

Wie messen ?

Das einfachste ist daher, über ein Programm immer wieder LDAP-Abfragen an die fraglichen Server zu stellen und die Zeit hierfür zu müssen. Es ist mit VBScript und PowerShell natürlich sehr einfach, eine LDAP-Verbindung zu einem DC aufzubauen und immer wieder einen Wert zu lesen. Um nicht lange eine LDAP-Suche zu bemühen, habe ich ein Programm geschrieben, welches sich einfach mit der RootDSE verbindet und die höchsten USN ausliest. Das habe ich auf PRTG:USNChange schon für einen Monitor verwendet. Damit war der Code dann schnell gebaut:

param(
   [string]$dc="nawdc001.netatwork.de"
)

write-host "end2end-ldap: Start"

$adsipath="LDAP://"+$dc+"/RootDSE"
write-host "end2end-ldap: LDAPPath" $adsipath 
$rootdse = [ADSI]$adsipath
write-host "end2end-ldap: Ping   :" -nonewline
(measure-command {test-connection $dc -count 1}).totalseconds
write-host "end2end-ldap: LDAP   :" -nonewline
(measure-command {$rootdse.highestCommittedUSN}).totalseconds
write-host "end2end-ldap: Dispose:" -nonewline
(measure-command {$rootdse.psbase.dispose()}).totalseconds
write-host "end2end-ldap: Ping   :" -nonewline
(measure-command {test-connection $dc -count 1}).totalseconds

Das Script sendet erst einen PING und dann die Anfragen um danach die Verbindung aktiv mit einem "DISPOSE" zu beendet und wieder ein PING zu senden". Im Wireshark ist das dann auch gut zu sehen. die ICMP-Pakete sind ganz hilfreich um bei solchen Mitschnitt den Anfang und Ende zu finden.

Man sieht hier recht einfach, wie schnell so etwas gehen kann

  • 1/2  der IMCP Ping (1ms)
  • 3/4/5  der TCP Handshake (1ms)
  • 5-13  die eigentliche LDAP Aktion mit Authentifizierung und Abfrage (28ms)
  • 14-16 Der Verbindungsabbau (1ms)
  • 17/18 der abschließende Ping (1ms)

Die Verzögerungen zwischen den Blöcken sind durch die PowerShell Ausführung verursacht. Die ganze Aktion hat aber nicht länger als 77ms gedauert und nicht wirklich Last auf dem DC erzeugt.

Früher konnte man auch per 138/UDP einen "LDAP-Ping" an einen DC senden. Das ist seit Windows 2008 nicht mehr der Fall.

Dauerhaft messen

Diesen einfachen Code ohne die ICMP-Pings drum herum habe ich nun in eine Skript verpackt, welches jede Sekunde eine Anfrage stellt und die Zeit dazu misst. Weiterhin führt es einen schleifenden Mittelwert mit, d.h. die letzten 10 Messungen werden gemittelt, so dass Ausreißer schneller erkannt werden können. Dieses Dauerskript läuft dann auf dem Client um die Stabilität und Performance des interessanten Domaincontrollers zu überwachen und auszugeben. Parallel werden Alarme im Eventlog geschrieben und eine CSV-Datei enthält die Daten. Dabei habe ich einen interessanten Beifang gemacht. Hier mal die Ausgabe von zwei verschiedenen Systemen:

Wenn sie das linke Bild anschauen, dann stellen Sie fest, dass immer wieder ein "Ausreiser" dabei ist. Über einen längeren Zeitraum kann man gut erkennen, dass die alle 46 Sekunden der Fall ist. Auf der Suche nach der Ursache bin ich dann auf die NetBIOS-Namensabfrage gestoßen. Der "Effekt" war nämlich weg, wenn der LDAP-Server per IP-Adresse angesprochen wurde, in der LMHOSTS eingetragen war oder NetBIOS auf der Netzwerkkarte deaktiviert war. Im Wireshark war sehr gut zu sehen, das der Client trotz erfolgreicher DNS-Auflösung alle 45 Sekunden ("NegativeCachePeriod") eine WINS-Broadcast Anfrage nach dem FQDN des Servers stellt. Auf Windows 2012/Windows 8 als Client war der Effekt nicht zu sehen. Windows 2008 und Windows 7 hingegen haben das Verhalten gezeigt. Den genauen Grund konnte ich nicht weiter ermitteln.

Laut Microsoft würden ältere Windows Systeme eine WINS-Auflösung für Namen mit 15 oder weniger Stellen durchführen, selbst wenn es ein per DNS auflösbarer FQDN ist, also mit "Punkten" in Namen, die so in WINS eher nicht gebräuchlich sind.

Interessant ist es auch, wie sich ein DC verhält, wenn die CPU-Last z.B. mit End2End-CPU auf 100% bringt. Die LDAP-Antwortzeiten werden schon länger aber zumindest auf meinem DC bleibt immer noch genug Zeit für die Beantwortung. Hier kommt es auch mehr auf den internen Scheduler und die Priorität der Prozesse an. Ich denke ein LSASS.EXE als System wird schon bevorzugt im Gegensatz zu einem PowerShell als User.

Download

Die Überwachung ist "natürlich" wieder ein kleines PowerShell-Script, dessen Code ihnen von End2End-File und anderen ähnlichen Skripten bekannt sein dürfte

end2end/end2end-ldap.ps1
Nach dem Download bitte die Erweiterung ".TXT" entfernen.

Das Skript können Sie selbst als Anwender auf jedem Client aufrufen, der per ADSI einen DC erreicht. Der einzige Parameter ist aktuell der Name des DCs. Wenn Sie den nicht angeben, dann versucht der dc.msxfaq.local zu erreichen. Da werden Sie sicher keine Antwort bekommen. Zudem protokolliert das Skript lokal die Ergebnisse in eine CSV-Datei. Da all dies im Source Code vorliegt, können Sie das Skript für eigene Zwecke recht einfach anpassen.

Weitere Links