Hausarbeit
Praxisteil¶
Bearbeiten Sie die Aufgaben in den dafür vorgesehenen Zellen und laden Sie ihre Datei als .ipynb hoch. Sie können diese Datei über “File > Download as… > Notebook” herunterladen. Achten Sie darauf, dass Ihre Abgabe keine Syntaxfehler enthält. Dies können Sie einfach durch ausführen der Zellen (“Run”) verifizieren.
In den Python-Zellen finden Sie einige Beispiele, die Sie zur Verifikation ihrer Abgabe nutzen können, indem Sie sie einkommentieren. Die Beispiele sollten im Erfolgsfall True zurück geben. Dies garantiert nicht automatisch die volle Punktzahl, dient aber zur Kontrolle für Sie.
Aufgabe 1: Fragmentierung von PDUs (2 Punkte)¶
Ändern Sie die folgende Funktion Fragment so ab, dass sie zu gegebenen Binärdaten (PDU_data, ein bytes-Objekt) und einer Größe max_len in Bytes eine Liste von 2-Tupeln zurück gibt. Das erste Element jedes Tupels beschreibt den Abstand (offset) vom Beginn der Daten, gemessen in Bytes. Das zweite Element enthält die entsprechenden max_len Bytes der usprünglichen Daten aus PDU_data. Das letzte Fragment kann weniger als max_len bytes enthalten – nutzen Sie also kein Padding.
In [ ]:
def Fragment(max_len, PDU_data):
to_return = []
# …
return to_return
# Beispiel zur Kontrolle
# print(Fragment(5, b”\xc5\xae\x30\x39\x56″) == [(0, b”\xc5\xae\x30\x39\x56″)])
# print(Fragment(5, b”\xc5\xae\x30\x39\x56\x49\x8f\x86\x00\x00\x00\x00\xa0\x02\x72\x10\x01\x6c\x00\x00\x02″) == [(0, b’\xc5\xae09V’), (5, b’I\x8f\x86\x00\x00′), (10, b’\x00\x00\xa0\x02r’), (15, b’\x10\x01l\x00\x00′), (20, b’\x02′)])
# print(Fragment(3, b”\xc5\xae\x30\x39\x56\x49\x8f\x86\x00\x00\x00\x00\xa0\x02\x72\x10\x01″) == [(0, b’\xc5\xae0′), (3, b’9VI’), (6, b’\x8f\x86\x00′), (9, b’\x00\x00\x00′), (12, b’\xa0\x02r’), (15, b’\x10\x01′)])
# print(Fragment(23, b””) == [])
Aufgabe 2: NAT (2 Punkte)¶
Die Funktion NAT_TCP_DPort soll Teil einer NAT-Implementierung werden. Diese Funktion übernimmt die Aufgabe, den Ziel-Port eines bestehenden TCP-Segments zu ändern. Die IP-Adresse wird hier nicht betrachtet.
Schreiben Sie die Funktion so um, dass sie den Parameter new_port vom Typ int als neuen Port im ebenfalls übergebenen TCP-Segment tcp_segment (bytearray-Objekt) setzt. Für das Setzen der passenden Bytes können Sie die int.to_bytes-Funktion (z.B. (80).to_bytes(2, byteorder=”big”)) nutzen. Ein bytearray-Objekt lässt Zuweisung einzelner Elemente (Bytes) im Gegensatz zu einem byte-Objekt zu.
Hinweis: Ein bytearray-Objekt ist das schreibbare Gegenstück zu bytes-Objekten.
Hinweis: Ein Bereich von Bytes lässt sich über die Syntax byteobj[from:to] = new_bytes schreiben, wobei from und to ganzzahlige Indizes sind.
In [ ]:
def NAT_TCP_DPort(new_port, tcp_segment):
# …
return tcp_segment
# Beispiele zur Kontrolle
# print(NAT_TCP_DPort(8888, bytearray(b’\x9b\x20\x00\x50\x5a\xbe\x08\x22\xa2\x82\x22\xed\x80\x10\x00\xed\x27\xaf\x00\x00\x01\x01\x08\x0a\xa1\xeb\x97\x49\x1c\xf4\x7e\xb9′)) == bytearray(b’\x9b “\xb8Z\xbe\x08″\xa2\x82″\xed\x80\x10\x00\xed\’\xaf\x00\x00\x01\x01\x08\n\xa1\xeb\x97I\x1c\xf4~\xb9′))
Aufgabe 3: TCP-Fenstergröße (4 Punkte)¶
Vervollständigen Sie die Funktion Recv_Window_Size so, dass sie für einen TCP-Header tcp_header als byte-Objekt die Fenstergröße (Window Size) zurück gibt. Bei den Verbindungen kann die TCP-Erweiterung Window Scale Option gesetzt sein. In diesem Fall wird shift > 0 übergeben – andernfalls ist shift == 0. shift entspricht shift.cnt aus dem RFC 1323 Sec. 2.
Hinweis: Für die Extraktion der nötigen Daten aus dem Header können Sie die int.from_bytes-Funktion nutzen. Die Daten liegen in Network-Byteorder (also “Big Endian”) vor.
In [ ]:
def Recv_Window_Size(tcp_header, shift):
# …
return 0
# Beispiel zur Kontrolle
# print(Recv_Window_Size(b”\x00\x50\x9b\x20\xa2\x82\x21\x98\x5a\xbe\x08\x22\x80\x18\x00\xe3\xaf\x73\x00\x00\x01\x01\x08\x0a\x1c\xf4\x7e\xb9\xa1\xeb\x96\xe4″, 0) == 227)
# print(Recv_Window_Size(b”\x9b\x20\x00\x50\x5a\xbe\x07\xd6\xa2\x82\x21\x98\x80\x18\x00\xe5\x27\xfb\x00\x00\x01\x01\x08\x0a\xa1\xeb\x96\xe4\x1c\xf4\x7e\x9f”, 7) == 29312)
Aufgabe 4: Schutz gegen IPv4-Spoofing (4 Punkte)¶
Die Funktion In_Subnet ist Teil der IPv4-Implementierung eines Routers. Sie soll bestimmen, ob ein ankommendes IP-Paket zu einem Subnetz passt, z.B. um IP-Spoofing zu verhindern.
Die Funktion nimmt die 32 Bits eines Subnetzes als byte-Objekt und einen IPv4-Header, ebenfalls als byte-Objekt entgegen. Ergänzen Sie die Funktion so, dass sie (als True bzw. False) zurückgibt, ob die Quell-Adresse des zum IPv4-Header gehörenden Paketes im Subnetz liegt oder nicht. Implementieren Sie nur den Fall für Subnetze mit der Maske 255.255.192.0 (oder /18 in CIDR-Notation).
Hinweis: Einzelne Bits lassen sich leicht mit Hilfe der &-Operation auswählen. Zum Beispiel ignoriert 0b11111100 & a_byte die unteren zwei Bits von a_byte.
In [ ]:
def In_Subnet(subnet, ipv4_header):
return False
# Beispiel zur Kontrolle
# print(In_Subnet(b’\xd1\x33\x80\x00′, b’\x45\x00\x01\x89\x5f\x30\x40\x00\x33\x06\xbf\xb6\xd1\x33\xbc\x94\xc0\xa8\xd9\x17′))
# print(not In_Subnet(b’\xd1\x33\xc0\x00′, b’\x45\x00\x01\x89\x5f\x30\x40\x00\x33\x06\xbf\xb6\xd1\x33\xbc\x94\xc0\xa8\xd9\x17′))
# print(not In_Subnet(b’\xd1\x32\x80\x00′, b’\x45\x00\x01\x89\x5f\x30\x40\x00\x33\x06\xbf\xb6\xd1\x33\xbc\x94\xc0\xa8\xd9\x17′))
Aufgabe 5: TCP Reno (3 Punkte)¶
Die Funktion Congestion_Window soll ein Tupel ausgeben, das zur Größe eines bisherigen Congestion Window (in MSS gemessen), dem aktuellen Threshold und der Information, ob in der aktuellen Runde ein Timeout aufgetreten ist (timeout) oder bereits duplicate_acks_count ACK-Duplikate eingetroffen sind, die neue Größe des Congestion Windows und den neuen Threshold als 2-Tupel zurück gibt. Als zugrundeliegender Algorithmus soll TCP Reno dienen.
Hinweis: Sie können davon ausgehen, dass ein Timeout oder ein ACK-Duplikat (oder nichts von beidem) in einer Runde vorkommt, jedoch nicht beides gleichzeitig.
In [ ]:
def Congestion_Window(congwin, threshold, timeout, duplicate_acks_count):
new_threshold = 0
new_congwin = 0
# …
return (new_congwin, new_threshold)
# Beispiele zur Kontrolle
# print(Congestion_Window(32, 16, False, 0) == (33, 16))
# print(Congestion_Window(32, 8, True, 1) == (1, 16))
# print(Congestion_Window(32, 10, False, 3) == (19, 16))
Aufgabe 6: Konstruktion eines HTTP-Request-Headers (3 Punkte)¶
Konstruieren Sie einen HTTP 1.1 Request als Zeichenkette aus der Methode, dem Pfad, dem Host und dem übergebenen Inhalt. Setzen Sie dabei das Host-Feld und das Content-Length-Feld. Gehen Sie davon aus, dass der Pfad bereits korrekt kodiert wurde. Der Inhalt (payload) wird als byte-Objekt übergeben. Nutzen Sie die ASCII-Kodierung für den Inhalt.
In [ ]:
def HTTP_Request(method, path, host, payload):
return “”
# Beispiel zur Kontrolle
# print(HTTP_Request(“POST”, “/sessions”, “example.com”, bytes([123, 112, 97, 115, 115, 119, 111, 114, 100, 58, 32, 39, 115, 117, 112, 101, 114, 32, 115, 101, 99, 114, 101, 116, 39, 125])).startswith(‘POST /sessions HTTP/1.1\r\n’))
# print(HTTP_Request(“POST”, “/sessions”, “example.com”, bytes([123, 112])).count(‘\r\n\r\n’) == 1)
Aufgabe 7: DNS-Anfrage (8 Punke)¶
Erstellen Sie in der folgenden Funktion Generate_Request ein bytearray-Objekt, das eine gültige DNS-Anfrage nach RFC 1035 erstellt und geben Sie es zurück. Den Aufbau der PDU zeigt Sec. 4.1, das Format einer PCI ist in Sec. 4.1.1 beschrieben. Das Format der UD ist in Sec. 4.1.2 beschrieben. Dabei sollen folgende Rahmenbedigungen gelten, die die Implementierung deutlich vereinfachen:
Es handelt sich um eine Anfrage nach einem Ressource Record vom Typ A (entspricht QTYPE). Die QCLASS ist IN. Die passenden (Binär-)Werte finden Sie ebenfalls im RFC.
Sie können der Einfachheit halber eine feste ID vergeben.
Es handelt sich um ein QUERY (OPCODE ist 0).
Gehen Sie bei QDCOUNT von 1 aus. Der Request hat nur ein “Query”, d.h. ANCOUNT, NSCOUNT und ARCOUNT sind 0. Die Domain dazu wird der Funktion bereits aufgeteilt (als “labels”, siehe Sec. 4.1.2) übergeben.
Hinweis: Sie können zum erstellen des Feldes QNAME die Hilfsfunktion Helper_QNAME nutzen. Beenden Sie QNAME mit einem Byte mit dem Wert 0x0: “The domain name terminates with the zero length octet for the null label of the root”. Das übernimmt die Hilfsfunktion nicht!
Hinweis: Mit byteobj += bytes([0b01010101, …]) lassen sich beliebig Bytes an das byteobj vom Typ bytearray anhängen. Alternativ können Sie auch wie in Aufgabe 2 mit int.to_bytes() arbeiten.
In [ ]:
def Helper_QNAME(labels):
qname = bytearray()
for i in labels:
qname += bytes([len(i)])
qname += bytes(i, “ASCII”)
return qname
def Generate_Request(labels):
request = bytearray()
# request += bytes([x, y]) # zwei Bytes ID setzen
# …
return request
# Beispiel zur Kontrolle
# print(Generate_Request([“www”, “lmu”, “de”]).endswith(b’\x03www\x03lmu\x02de\x00\x00\x01\x00\x01′))
# print(Generate_Request([“www”, “lmu”, “de”])[4:6] == bytes([0x00, 0x01]))
# print(len(Generate_Request([“www”, “lmu”, “de”])) == 28)
In [ ]: