HTTP Chunked

Für einen Office 365 Test interessiert mich natürlich, ob auf dem Weg von meinem Client zum Service in der Cloud eine Firewall, Proxy Server oder ein NAT-Router o.ä. die TCP-Verbindung unterbricht. Das einfachste ist dabei der Zugriff auf eine Webseite, die immer wieder Daten liefert und dabei natürlich Pausen einlegt. Ich habe auf der Seite TCP/HTTP Session Timeout und 120 Sekunden schon einiges zu Timeouts auf TCP-Ebene geschrieben. Die meisten Office 365 Dienste nutzen aber HTTP oder HTTPS. Und da habe ich mir dann doch ein kleinen PHP-Script gebaut, was ich in Azure oder einem Shared Hosting einfach aufspielen kann.

Funktionsweise

Die Funktion ist schnell erklärt: Ich starte einen Browser und spreche die PHP-Seite über eine URL an. Die PHP -Seite liefert dann einfach immer wieder einen Timestamp" und vergrößert den Abstand zwischen den Meldungen immer weiter. Damit die HTTP-Verbindung bestehen bleibt, nutze ich den "CHUNKED"-Transfer, wie ihn auch Streaming-Dienste und auch Outlook und ActiveSync nutzen. Beim klassischen Webseitenaufruf versucht der Server die Daten so schnell wie möglich zu liefert und der Client zeigt diese an.

Die HTTP-Übertragung ist danach beendet und wenn keine weiteren Anfragen an den Server gestellt werden. dann baut der Server oder Client die Verbindung nach einige Sekunden oder Minuten wieder ab. Das ist nicht der Fall, wenn Hintergrundprozesse auf Daten der Gegenseite warten. Der Client stellt dann eine Anfrage aber der Server beantwortet diese nicht abschließend, sondern kann immer weiter Daten übermitteln. Insofern ist der Begriff "Push-Mail" bei ActiveSync aber auch MAPI/HTTP falsch. Der Server kann keine Verbindung zum Client aufbauen sondern der Client stellt eine Anfrage und der Server lässt sich einfach lange Zeit.

Dumm nur, wenn dann ein System dazwischen die TCP-Verbindung wegen "Inaktivität" verwirft und dann eine später eintreffende Antwort des Servers nicht mehr zum Client weitergeben kann. Der Client wartet vergeblich auf die Antwort des Servers. Gute Clients warten natürlich nicht ewig, sondern fragen nach einem Timeout noch mal nach und können so erkennen, dass Sie was verpasst haben und ihr Verhalten anpassen.

Für ein Office 365 Netzwerk Assessment ist es daher wichtig zu wissen, wie lange eine TCP und insbesondere eine HTTP-Verbindung bestehen bleiben kann, ohne dass eine Komponente dazwischen die Verbindung unbemerkt kappt. Microsoft selbst empfiehlt hier zumindest einen Timeout von 120 Sekunden.

Ich habe mir ein Skript gebaut, welches eben nun vom Client angesprochen wird und mit zunehmenden Abständen eine Ausgabe an den Client sendet. Irgendwann bleiben die Ausgaben aus.

Ansicht im Browsers

Die Nutzung ist von jedem Client, egal ob Windows Unix, Mobilgerät, mit jedem Browser möglich. Nach Eingabe der URL wird auf dem Server das PHP-Script gestartet und sendet eine Information und dann immer wieder einen Zeitstempel. Es handelt sich dabei um die Serverzeit.

Hinweis:
Die URL in dem Bild ist nicht gültig. Die MSXFAQ funktioniert ohne PHP-Skripte. Siehe auch msexchangefaq.de hacked

Sie sehen hier den Start-Zeitpunkt und dann den nächsten Timestamp nach 1, 5, 10, 65, 95, 125 Sekunden. Aber der Timestamp nach 245 Sekunden ist ausgeblieben. Auf dem Weg von mir zum Server gibt es also etwas, was zwischen 124 und 245 Sekunden (also ca. 2-4 Minuten) keine Daten mehr zurückkommen lässt.

Ansicht in Wireshark

Wenn Sie nun parallel mit Wireshark die TCP-Verbindung mitschneiden, dann sehen wir interessante Details.

Den TCP und TLS-Handshake habe ich weg gelassen und bei 0.154 kommt die erste Info und dann nach 1,147 und 6,148 und 16,150 Sekunden  die drei Zeitstempel für 1 Sek, 5Sek und 10 Sek. ehe aber der 65 Sek Zeitstempel bei 81.147 Sekunden kommt, sehen wir bei 61.151Sek ein Keep-Alive. Der TCP-Stack des Clients (Chrome 67) sendet ein aktives TCP-Alive, was auch beantwortet wird. Es kann also weiter geben. Bei Sekunde 176 kommt dann der Timestamp nach 95 Sekunden Wartezeit und auch noch 125 Sekunden später bei 301 Sekunden seit Start des Mitschnitts kommen noch ein weiterer Zeitstempel an. Aber dann stehen 245 Sekunden Wartezeit an, also etwas mehr als 4 Minuten. Im Browser ist nichts zu sehen aber auf dem Netzwerk schon. Bei 180 Sekunden sendet der WebServer Daten, die im Browser aber nicht erscheinen.

Dann gibt es mehrere Keep-Alive-Pakete ehe bei 661 Min, (also ca. 360 Min nach dem letzten erklärbaren Paket). Daten kommen, die aber mit einem "Encryption Alert" von Wireshark gekennzeichnet werden. Danach baut der Client die Verbindung ab.

Ich habe das dann noch mal per HTTP mitgeschnitten, um auch den Payload in Wireshark zu sehen. Hier sehen Sie bei Minute 301 noch die Meldung, dass die Nächste Antwort nun nach 245 Sekunden kommen soll. Dann folgten noch einige TCP Keep-Alive-Pakete im Abstand von 45 Sekunden  (346 und 391 und 436 Sekunden).

Bei Sekunde 481 ist es dann aber dem WebServer zu viel und er beendet die Verbindung mit einem [FIN, ACK], also ca. 150 Sekunden nach der letzten Nutzinformation. Ob das nun am WebServer oder eine Zwischenstation liegt, kann ich an dem Paket nicht zuverlässig ausmachen, da auch Source-IP-Adressen problemlos gefälscht werden können.

Ein Gegencheck gegen einen Server im gleichen LAN (hier eine Synology DS216j) zeigt, dass es sehr wohl möglich ist, so lange HTTP-Verbindungen aufrecht zu erhalten:

Ein Versuch die Daten per Fiddler weiter zu analysieren misslang. Anscheinend versteht Fiddler mein "Chunked Mode" nicht und zeigt gar nichts an, bis die Requests irgendwann abgeschlossen sind. Insofern sind solche Tests entweder per HTTP mit Wireshark oder nur im Browser oder anderem HTTP-Client möglich.

Code auf dem Server

Ich habe zuerst mit PowerShell und dem HTTPListener (Siehe PowerShell als HTTPServer) eine Lösung gestartet aber sehr schnell dies wieder eingestellt. Der Aufwand für dein Einsatz ist einfach zu hoch. Sie müssten auf einem PC die PowerShell starten und aktiv lassen und zudem die Firewall öffnen. Da ist eine ASPX oder eben PHP-Version deutlich flexibler und einfacher einzusetzen. Die meisten Webhosting-Systeme stellen PHP bereit und so reicht ein einfacher Upload der Datei auf ihren Webspace in der Regel, um eine eigene Gegenstelle zu starten. Sogar kleine Geräte wie ein RaspberryPi oder ein NAS-System unterstützten PHP und Azure natürlich auch. Daher wurde es ein PHP Script. Hier der Code als Vorschau:

Sie müssen den Code natürlich nicht abtippen und der Code auf dem Bild ist auch nicht immer aktuell.

Hier die PHP Datei als ZIP-Archiv:
http_chunked.php.zip
Kleine Anmerkung: Einige Webserver parsen auch TXT-Dateien und führen diese aus. Daher ist es nun eine ZIP-Datei

Sie sollten diese PHP-Datei auf jedem WebServer einsetzen können, der PHP unterstützt. Beachten Sie aber die Einschränkungen am Ende der Seite.

Ich bin sicher, dass irgendwann jemand das Skript noch mit einer clientseitigen JavaScript-Komponente ergänzt, so dass der Browser auch ohne Zutun des Servers einen Countdown o.ä. anzeigt und so noch einfacher sichtbar wird, wenn ein Paket des Webservers ausbleibt.

Einschränkungen

Nicht immer ist es die Firewall, die eine Messung unbrauchbar macht. Es gibt noch andere Komponenten, die gezielt auf mein PHP-Skript einwirken und die Aussagekraft der Ausgabe relativieren. Was ich bislang gefunden haben

  • Chunked und Server-Buffering
    Anscheinend folgende nicht alle WebServer den Vorgaben von PHP und cachen trotzdem Antworten um diese dann erst zu senden, wenn sie "komplett" sind. Sie bemerken das meist derart, dass gar keine Antwort kommt und das Browserfenster leer bleibt und wartet.
  • Server und Kompression (ZIP)
    Wenn der Webserver ein Modul zum komprimieren der Daten aktiv hat, dann kann auch das die Rückgabe per "Chunking" verhindern. Das MOD_DEFLATE-Modul sammelt z.B. auch erst Daten um dieses dann komprimiert zu übertragen.
  • Client Rendering
    Die meisten Browser starten nicht sofort mit dem ersten Byte den Aufbau der Ausgabe sondern sammeln erst einige Bytes. Ich versuche das zu umgehen, indem ich am Anfang nach dem Start einfach 5000 Leerzeichen ausgebe. Damit scheinen die meisten Clients zu funktionieren.
  • PHP Timeout auf dem Server
    Das PHP-Skript wird vom WebServer natürlich Auch nicht endlos ausgeführt. Bei 1und1 ist es angeblich 60 Sekunden und könnte per HTACCESS hochgesetzt werden. Das kann aber nicht so ganz stimmen, da auch nach 125 Sek "Untätigkeit" immer noch eine Antwort kommt und alle Abschnitts zusammen schon 5 Minuten ergeben.
  • Verschlüsselung mit TLS/SSL und Proxy
    Den Aufruf der PHP-Datei ohne Verschlüsselung hat funktioniert während der Aufruf per HTTPS in Verbindung mit Fiddler keine Daten geliefert hat. Das kann auch mit jedem anderen Proxy passieren, insbesondere mit "Deep Inpection"

Sie sehen also, dass auch die mit meinem PHP-Skript und Browser ermittelten Daten und nicht ungeprüft übernommen werden würden

PowerShell als Server

Um nicht ganz von PHP und einem WebHoster abhängig zu sein, habe ich mir natürlich doch noch eine PowerShell-Script Version gebaut. Primär auch um zu lernen, wie ich mit dem HTTPListener auch Chunking nutzen kann. Das ist gar nicht so schwer. Allerdings unterstützt das hier entwickelte PowerShell-Skript, anders als eine PHP-Seite auf einem Apache, kein Multithreading. Es kann also immer nur eine Anfrage sequentiell bedienen.

PowerShell Sample
httpd_chunked.ps1

Insofern ist dies eher eine Demoapplikation für Chunked Transport mit PowerShell

Weitere Links