PyTCP ist ein voll funktionsfähiger TCP/IP-Stack, der in Python geschrieben ist. Es unterstützt den TCP-Stream-basierten Transport mit zuverlässiger Paketzustellung auf Basis eines Sliding-Window-Mechanismus und grundlegender Überlastungskontrolle. Es unterstützt auch IPv6/ICMPv6-Protokolle mit SLAAC-Adresskonfiguration. Es fungiert als User-Space-Programm, das an die Linux-TAP-Schnittstelle angeschlossen ist. Es verfügt über einfaches Routing und kann Datenverkehr über ein lokales Netzwerk und das Internet senden und empfangen.
Version 2.7 enthält im Gegensatz zu seinen Vorgängern den PyTCP-Stack-Code in Form einer Bibliothek, sodass er einfach importiert und von externem Code verwendet werden kann. Dies sollte die Benutzererfahrung reibungsloser machen und schließlich die volle Möglichkeit bieten, die Standard-Linux-Stack-Aufrufe (z. B. Socket-Bibliothek) durch die PyTCP-Aufrufe in jeder Drittanbieteranwendung zu ersetzen.
Dieses Projekt begann zunächst als rein pädagogische Maßnahme mit dem Ziel, meine Python-Kenntnisse zu verbessern und mein Netzwerkwissen als Teil der Vorbereitung auf die Rolle des Netzwerkingenieurs bei Facebook aufzufrischen. Seitdem ist es eher zu einem „Lieblingsprojekt“ geworden, dem ich in unregelmäßigen Abständen einen Teil meiner Zeit widme. Normalerweise werden jedoch alle ein bis zwei Monate ein paar Updates hinzugefügt.
Ich freue mich über jeden Beitrag und jede Hilfe von allen, die sich für Netzwerkprogrammierung interessieren. Jede Eingabe ist willkommen. Bedenken Sie außerdem, dass einige Stack-Funktionen möglicherweise nur teilweise implementiert sind (wie für den Stack-Betrieb erforderlich). Möglicherweise sind sie nicht optimal oder nicht 100 % RFC-konform implementiert (aus Zeitmangel) oder sie enthalten möglicherweise Fehler, die ich noch beheben muss.
Bitte schauen Sie sich auch meine beiden anderen verwandten Projekte an:
RusTCP – Versuchen Sie, einige der PyTCP-Funktionen in Rust neu zu schreiben und sie zum Erstellen eines IPv6/SRv6-Laborrouters zu verwenden.
SeaTCP – Versuch, mithilfe von C und Assembler-Sprachen einen Stapel mit geringer Latenz zu erstellen.
Funktionsprinzip und Testaufbau
Der PyTCP-Stack hängt von der Linux-TAP-Schnittstelle ab. Die TAP-Schnittstelle ist eine virtuelle Schnittstelle, die netzwerkseitig über eine Linux-Bridge oder Open vSwitch in die bestehende virtuelle Netzwerkinfrastruktur „eingebunden“ werden kann. Auf der internen Seite kann die TAP-Schnittstelle wie jede andere Netzwerkkarte verwendet werden, indem Pakete programmgesteuert an sie gesendet und von ihr empfangen werden.
Wenn Sie den PyTCP-Stack in Ihrem lokalen Netzwerk testen möchten, würde ich vorschlagen, das folgende Netzwerk-Setup zu erstellen, das es Ihnen ermöglicht, sowohl den Linux-Kernel (im Wesentlichen Ihr Linux-Betriebssystem) als auch den PyTCP-Stack gleichzeitig mit Ihrem lokalen Netzwerk zu verbinden .
Nachdem das Beispielprogramm (entweder Client oder Dienst) den Stack gestartet hat, kann es über vereinfachte BSD-Sockets wie die API-Schnittstelle mit ihm kommunizieren. Es besteht auch die Möglichkeit, Pakete direkt zu versenden, indem eine der _*_phtx() Methoden der PacketHandler -Klasse aufgerufen wird.
Klonen von PyTCP aus dem GitHub-Repository
In den meisten Fällen sollte PyTCP direkt aus dem GitHub-Repository geklont werden, da diese Art der Installation eine vollständige Entwicklungs- und Testumgebung bietet.
git clone http://github.com/ccie18643/PyTCP
Nach dem Klonen können wir eines der enthaltenen Beispiele ausführen:
Gehen Sie zum Stack-Stammverzeichnis (es heißt „PyTCP“).
Führen Sie bei Bedarf den Befehl sudo make bridge aus, um die Brücke „br0“ zu erstellen.
Führen Sie den Befehl sudo make tap aus, um die Schnittstelle tap7 zu erstellen und sie der Brücke „br0“ zuzuweisen.
Führen Sie den Befehl make aus, um die richtige virtuelle Umgebung für Entwicklung und Tests zu erstellen.
Laufen . venv/bin/activate -Befehl zum Aktivieren der virtuellen Umgebung.
Führen Sie ein beliebiges Beispiel aus, z. B. example/run_stack.py .
Drücken Sie Strg-C, um es zu stoppen.
Um verschiedene Stack-Betriebsparameter zu optimieren, bearbeiten Sie bitte die Datei pytcp/config.py entsprechend.
PyTCP aus dem PyPi-Repository installieren
PyTCP kann auch als reguläres Modul aus dem PyPi-Repository installiert werden.
python -m pip install PyTCP
Stellen Sie nach der Installation sicher, dass die TAP-Schnittstelle betriebsbereit ist und zur Bridge hinzugefügt wurde.
sudo ip tuntap add name tap7 mode tapsudo ip link set dev tap7 upsudo brctl addbr br0sudo brctl addif br0 tap7
Der PyTCP-Stack kann mit dem folgenden Code importiert und gestartet werden. Es startet die Stack-Subsysteme und konfiguriert automatisch sowohl die IPv4- als auch die IPv6-Protokolladressen mithilfe von DHCPv4 bzw. IPv6 SLAAC.
Die Stack-Subsysteme laufen in eigenen Threads. Nach dem Start gibt der Stack die Kontrolle wieder an den Benutzercode zurück und kann mit dem folgenden Aufruf gestoppt werden.
stack . stop ()
Merkmale
Bereits implementiert:
Stack – Schneller Paketparser mit „Zero Copy“-Ansatz.
Stack – Schneller Paketassembler mit „Zero Copy“-Ansatz.
Stack – Bibliothek zur Manipulation von MAC-Adressen – Kompatibel mit dem Pufferprotokoll (Memoryview).
Stack – IPv4-Adressmanipulationsbibliothek – Kompatibel mit dem Pufferprotokoll (Memoryview) und nicht abhängig von der Python-Standardbibliothek.
Stack – IPv6-Adressmanipulationsbibliothek – Kompatibel mit dem Pufferprotokoll (Memoryview) und nicht abhängig von der Python-Standardbibliothek.
Code – Unit-Tests für einige der Bibliotheken und Module (basierend auf dem Testslide-Framework von Facebook)
Ethernet-Protokoll – Unterstützung von Ethernet II-Standardrahmen.
Ethernet-Protokoll – Unicast, IPv4-Multicast, IPv6-Multicast und Broadcast-Adressierung.
ARP-Protokoll – ARP Probe/Announcement IP Conflict Detection (ACD)-Mechanismus.
IPv4-Protokoll – Standardrouting, der Stack kann mithilfe des IPv4-Protokolls über das Internet mit Hosts kommunizieren.
IPv4-Protokoll – Automatische IPv4-Adresskonfiguration mithilfe des DHCPv4-Protokolls.
IPv4-Protokoll – Defragmentierung eingehender Pakete, robuster Mechanismus, der in der Lage ist, nicht in der richtigen Reihenfolge befindliche und überlappende Datenfragmente zu verarbeiten.
IPv4-Protokoll – IPv4-Optionen akzeptiert, aber nicht unterstützt.
IPv4-Protokoll – Unterstützt mehrere Stack-IPv4-Adressen, jede von ihnen verhält sich so, wie sie einem separaten VRF zugewiesen wurde
ICMPv4-Protokoll – Die Echo-Request-, Echo-Reply- und Port-Unreachable-Nachrichten.
IPv6-Protokoll – Standardrouting, der Stack kann mithilfe des IPv6-Protokolls über das Internet mit Hosts kommunizieren.
IPv6-Protokoll – Automatische Link-Local-Adresskonfiguration mit EUI64 und Duplicate Address Detection.
IPv6-Protokoll – Automatische GUA-Adresskonfiguration mithilfe von Router Advertisement / EUI64.
IPv6-Protokoll – Automatische Zuweisung von Solicited Node Multicast-Adressen.
IPv6-Protokoll – Automatische Zuweisung von IPv6-Multicast-MAC-Adressen.
IPv6-Protokoll – Defragmentierung eingehender Pakete, robuster Mechanismus, der in der Lage ist, nicht in der richtigen Reihenfolge befindliche und überlappende Datenfragmente zu verarbeiten.
ICMPv6-Protokoll – Implementierung des Multicast Listener Discovery v2 (MLDv2)-Protokolls (nur vom Stack benötigte Nachrichten).
UDP-Protokoll – Volle Unterstützung. Der Stack kann über das UDP-Protokoll Daten mit anderen Hosts austauschen.
UDP-Sockets – Volle Unterstützung, Stacks „Endbenutzer“-API ähnlich den Berkeley-Sockets.
UDP-Dienste – Die Echo-, Discard- und Daytime-Dienste, die zu Testzwecken implementiert wurden (in „Beispielen“).
TCP-Protokoll – Vollständige Implementierung der TCP Finite State Machine. Zu diesem Zeitpunkt kann der Stack über das TCP-Protokoll Massendaten mit anderen Hosts austauschen.
TCP-Protokoll – Unterstützung der TCP-Optionen für: MSS, WSCALE, SACKPERM, TIMESTAMP.
TCP-Protokoll – TCP-Schiebefenstermechanismus mit Datenneuübertragung (schnelle Neuübertragung und zeitbasierte Szenarien).
TCP-Protokoll – erneute Übertragung von TCP-SYN/FIN-Paketen.
TCP-Sockets – Volle Unterstützung, Stacks „Endbenutzer“-API ähnlich den Berkeley-Sockets
Zu implementieren:
ICMPv6 – MLDv2-Unterstützung ist jetzt ziemlich durcheinander. Muss es beenden.
Testen – Paketflusstests (tests/packet_flow_ .py) müssen umgestaltet werden, um dasselbe Format und Verzeichnis wie FPA-Tests basierend auf test_frames zu verwenden.*
Testen – Erstellen Sie FPA-Komponententests für den MLDv2-Bericht (len, str,assemble).
IPv4 – Implementieren Sie die Paketdefragmentierung erneut, um ganze Pakete in der Flow-Datenbank zu speichern, anstatt Kopien des IP-Headers und der Daten zu erstellen.
Stack – Implementierung der RAW-Socket-Unterstützung – zur Verwendung beispielsweise durch den ICMP-Echo-Client.
Code – Unit-Tests für verbleibende Bibliotheken und Module (basierend auf der Testslide-Bibliothek von Facebook).
Code – Schreiben Sie die DHCPv4-Protokollunterstützung neu, um den Standard-FPA/FPP-Ansatz anstelle von Legacy-Code zu verwenden.
Stack – Kehren Sie zur Implementierung der Stack-Debugging-Konsole zurück, damit bestimmte Informationen zu Stack-Komponenten bei Bedarf durch Senden von Befehlen angezeigt werden können. z. B. „ICMPv6 und Cache anzeigen“, „IPv6-Route anzeigen“ usw. Sie sollten damit auch interaktive Befehle wie Ping oder Stacks UDP/TCP-Echo-Clients ausführen können.
QUIC-Protokoll – Recherche und Plan für die Umsetzung. Dies hängt von der Fähigkeit ab, eine Laborumgebung dafür zu erstellen.
IPv6-Protokoll – Neugestaltung der RA-PI-Optionsbehandlung und der automatischen ND-Präfixkonfiguration, um A- und L-Flags ordnungsgemäß zu verwenden. Es sind auch einige Nachforschungen erforderlich, wenn ein anderes Präfix als /64 angekündigt wird.
IPv6-Protokoll – Implementieren Sie die verbleibenden Erweiterungsheader.
IPv6-Protokoll – Validierung und möglicherweise Neuimplementierung bestimmter IPv6-Mechanismen/Prozesse gemäß RFC-Regeln.
IPv6-Protokoll – Untersuchen Sie den Hop-by-Hop-Options-Header und seine Beziehung zur MLD2-Berichtsnachricht und implementieren Sie ihn bei Bedarf, damit MLD2 ordnungsgemäß funktioniert.
ICMPv6-Protokoll – Vollständige Implementierung von Multicast Listener Discovery v2 (MLDv2) < – kann vom Stack benötigt werden, um auf MLD-Anfragen zu antworten.
TCP-Protokoll – Korrekte Handhabung von RST-Paketen in verschiedenen Zuständen. Das muss recherchiert werden. Hierzu liegt ein Fehlerbericht vor.
TCP-Protokoll – Der CLOSE-Systemaufrufmechanismus muss überarbeitet werden, damit das FIN-Flag auf das letzte Datenpaket gesetzt werden kann, anstatt in einem separaten Paket übertragen zu werden.
TCP-Protokoll – ACK-Paket-Neuübertragung für den Fall, dass wir eine FIN-Neuübertragung im TIME_WAIT-Status erhalten. Das muss untersucht werden.
TCP-Protokoll – implementiert die richtige Reaktion auf Pakete, die alte SEQ- und/oder ACK-Nummern enthalten. Das muss untersucht werden.
Protokollierung – Ersetzen Sie Loguru durch einen selbst entwickelten Logger, um Leistung und Flexibilität zu verbessern.
Stack – Konvertieren Sie den PyTCP-Stack in eine Bibliothek, damit externe Anwendungen ihn problemlos importieren können.
Stack – Paketflusszähler, die dabei helfen, Paketstatistiken zu sammeln und den Paketfluss für Unit-Tests nachzuverfolgen.
Stack – Implementieren Sie einen Feedback-Mechanismus für den TX-Pfad, damit Fehler beim Senden von Paketen an Sockets kommuniziert werden können.
IPv6-Protokoll – Möglichkeit, Datenverkehr über das Standard-Gateway an externe Ziele weiterzuleiten.
TCP-Protokoll – Stellen Sie sicher, dass die Ereigniskommunikation von der TCP-Sitzung zum Socket ordnungsgemäß funktioniert (z. B. Zurücksetzen der Verbindung durch Peer).
IPv4-Protokoll – Es ist eine Verbesserung des IP-Defragmentierungsmechanismus, der Handhabung von Fragmenten außerhalb der Reihenfolge und der Bereinigung verwaister Fragmente erforderlich.
UDP-Protokoll – Benötigen Sie einen UDP-Echo-Client und einen Mechanismus, um den Empfang einer ICMP-Port-Unreachable-Nachricht an den UDP-Socket zu melden.
UDP-Sockets – Es ist eine Überarbeitung erforderlich, damit die Endbenutzerschnittstelle besser mit den Berkeley-Sockets übereinstimmt, sodass Anwendungen von Drittanbietern sie ohne Portierung verwenden können.
TCP-Sockets – Es ist eine Überarbeitung erforderlich, damit die Endbenutzerschnittstelle besser mit den Berkeley-Sockets übereinstimmt, sodass Anwendungen von Drittanbietern sie ohne Portierung verwenden können.
Beispiele
Mehrere Ping-Pakete und zwei Monkeys wurden per TCP über das IPv6-Protokoll zugestellt.
Stack versucht, seine Link-Local-Adresse automatisch zu konfigurieren. Es generiert es als EUI64-Adresse. Als Teil des DAD-Prozesses tritt er der entsprechenden Multicast-Gruppe mit angeforderten Knoten bei und sendet eine Nachbaranfrage für seine generierte Adresse.
Stack erhält keine Neighbor Advertisement für die von ihm generierte Adresse und weist sie daher seiner Schnittstelle zu.
Stack versucht, eine vorkonfigurierte statische Adresse zuzuweisen. Als Teil des DAD-Prozesses schließt es sich der entsprechenden Multicast-Gruppe mit angeforderten Knoten an und sendet eine Nachbaranfrage für die statische Adresse.
Ein anderer Host mit derselben bereits zugewiesenen Adresse antwortet mit einer Neighbor Advertisement-Nachricht. Dadurch wird dem Stack mitgeteilt, dass die Adresse, die er zuzuweisen versucht, bereits von einem anderen Host zugewiesen wurde, sodass der Stack sie nicht verwenden kann.
Stack sendet eine Router-Solicitation-Nachricht, um zu prüfen, ob globale Präfixe verwendet werden sollten.
Der Router antwortet mit einer Router-Ankündigung, die ein zusätzliches Präfix enthält.
Stack versucht, eine basierend auf dem empfangenen Präfix und dem EUI64-Hostteil generierte Adresse zuzuweisen. Als Teil des DAD-Prozesses schließt es sich der entsprechenden Multicast-Gruppe mit angeforderten Knoten an und sendet eine Nachbaranfrage für die statische Adresse.
Stack erhält keine Neighbor Advertisement für die von ihm generierte Adresse und weist sie daher seiner Schnittstelle zu.
Nachdem alle Adressen zugewiesen wurden, sendet der Stack einen weiteren Multicast-Listener-Bericht, der alle Multicast-Adressen auflistet, die er abhören möchte.
TCP Fast Retransmit in Aktion nach verlorenem TX-Paket.
Ausgehendes Paket geht aufgrund eines simulierten Paketverlustmechanismus „verloren“.
Peer bemerkt die Inkonsistenz in den Paket-SEQ-Nummern und sendet eine „Fast Retransmit Request“ aus.
Stack empfängt die Anfrage und überträgt das verlorene Paket erneut.
Außerordentliche Warteschlange in Aktion während eines RX-Paketverlustereignisses
Eingehende Pakete gehen aufgrund eines simulierten Paketverlustmechanismus „verloren“.
Stack bemerkt eine Inkonsistenz in der SEQ-Nummer des eingehenden Pakets und sendet eine „Fast Retransmit“-Anfrage.
Bevor der Peer die Anfrage erhält, sendet er mehrere Pakete mit höherer SEQ als vom Stack erwartet. Der Stack stellt alle diese Pakete in die Warteschlange.
Der Peer überträgt das verlorene Paket erneut.
Stack empfängt das verlorene Paket, ruft alle in der Out-of-Order-Warteschlange gespeicherten Pakete ab und verarbeitet sie.
Stacks sendet ein ACK-Paket, um die zuletzt aus der Warteschlange gezogenen Pakete zu bestätigen.
TCP Finite State Machine – Stack führt den TCP Echo-Dienst aus.
Peer öffnet die Verbindung.
Peer sendet Daten.
Stack gibt die Daten zurück.
Peer schließt die Verbindung.
TCP Finite State Machine – Stack führt den TCP Echo-Client aus.
Stack öffnet die Verbindung.
Stack sendet Daten.
Peer gibt die Daten zurück.
Stack schließt die Verbindung.
Paket-Sicherheitsprüfungen vorab in Aktion durchführen.
Der erste Screenshot zeigt den Stapel mit deaktivierter Plausibilitätsprüfung. Ein fehlerhaftes ICMPv6-Paket kann zum Absturz führen.
Der zweite Screenshot zeigt den Stapel mit aktivierter Plausibilitätsprüfung. Ein fehlerhaftes ICMPv6-Paket wird verworfen, bevor es an den ICMPv6-Protokollparser weitergeleitet wird.
Der dritte Screenshot zeigt das fehlerhafte Paket. Das Feld „Anzahl der MA-Datensätze“ wurde auf 777 gesetzt, obwohl das Paket nur einen Datensatz enthält.
ARP-Probe-/Ankündigungsmechanismus.
Stack verwendet ARP-Probes, um mögliche Konflikte für jede konfigurierte IP-Adresse zu finden.
Eine der IP-Adressen (192.168.9.102) ist bereits vergeben, daher wird der Stack darüber benachrichtigt und überspringt sie.
Die restlichen IP-Adressen sind kostenlos, daher beansprucht der Stack sie, indem er für jede von ihnen eine ARP-Ankündigung sendet.
ARP-Auflösung und Umgang mit Ping-Paketen.
Host 192.168.9.20 versucht, den Stack anzupingen. Dazu sendet er zunächst ein ARP-Request-Paket, um die MAC-Adresse des Stacks herauszufinden.
Der Stack antwortet, indem er ein ARP-Antwortpaket sendet (der Stack muss seine Anfrage nicht senden, da er sich den MAC des Hosts bereits aus der Anfrage des Hosts notiert hat).
Der Host sendet Ping-Pakete und der Stack antwortet darauf.
IP-Fragmentierung.
Der Host sendet ein 4-KB-UDP-Datagramm mit drei fragmentierten IP-Paketen (drei Fragmente).
Der Stack empfängt Pakete, setzt sie zu einem Ganzen zusammen und leitet sie dann (über den UDP-Protokoll-Handler und den UDP-Socket) an den UDO-Echo-Dienst weiter.
Der UDP-Echo-Dienst erfasst Daten und legt sie wieder im UDP-Socket ab.
Das UDP-Datagramm wird an den IP-Protokoll-Handler weitergeleitet, der ein IP-Paket erstellt. Nachdem überprüft wurde, ob es die Verbindung überschreitet, fragmentiert MTU es in drei separate IP-Pakete.
IP-Pakete werden in Ethernet-Frames gekapselt und auf einen TX-Ring gelegt.