Forum: PC-Programmierung PACKET Sockets - TCP/IP-Handshake


von Matthias (Gast)


Lesenswert?

Hallo.

Ich habe mich mal an das Thema Socket-Programmierung in C herangewagt, 
nicht weil ich ein konkretes Projekt realisieren wollte, sondern 
vielmehr, weil ich lernen wollte, was unter der Haube meines Browsers 
und E-Mail-Clients im Verborgenen vor sich geht.

Hierbei habe ich mich im Wesentlichen auf zwei Kommunikationswege 
beschränkt. Die klassische tcp/ip-Verbindung von einem Client zu einem 
Server auf Port 80 und das Senden und Empfangen von E-Mails in Form 
eines eigenen E-Mail-Clients.

Mit dem entsprechenden Einarbeitungs- und Arbeitsaufwand ist mir das 
auch gut gelungen. Mittels openssl habe ich dann auch gleich die 
Verschlüsselung in C implementiert.

Angefangen bei 'normalen' StreamSockets, über RawSockets, habe ich 
vieles ausprobiert, mit Erfolg.

Mein letztes Projekt besteht nun darin, dass ich mittels
1
packet_socket = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)
meinen eigenen tcp/ip-Handshake via selbstgebautem Client durchführen 
will.

Weil das Thema sehr komplex ist, habe ich mich erst einmal auf mein 
eigenes Netzwerk beschränkt, in dem ich einen eigenen Server aufgesetzt 
habe, welchen ich dann über das interface 'wlan0' und die bekannte IP, 
wie auch MAC ansprechen wollte. Gesagt getan, dass Programm ist fertig, 
reagiert aber nicht wie erwartet.

Ich setze alle Header (ETH;IP;TCP) zusammen, die Pruefsummen werden 
berechnet und stimmen, somit sendet sendto() ein Paket ohne Payload, bei 
dem das SYN-Bit gesetzt ist, um den Handshake einzuleiten.

Da ich bei der nun folgenden Kommunikation auf meinem Rechner bleibe, 
die IP und MAC kenne, kein Router oder weitere Stationen 
zwischengeschaltet sind, sollte es relativ problemlos von statten gehen, 
dachte ich.

Mein Programm meldet mir, dass es ein TCP-Paket gesendet hat und 
Wireshark bestätigt, ja ein Paket wurde gesendet. Allerdings gibt es 
zwei Probleme. Einmal erhalte ich nicht die gewünschte Reaktion auf 
meinen Handshake-Versuch, so sehe ich bspw. gar keine tcp/ip-Daten in 
Wireshark. Darüber hinaus ist mir noch aufgefallen, dass mein Programm 
gar nicht, wie geplant, von 127.0.0.1 zu 127.0.0.1 sendet, sondern 
zuerst einmal einen Broadcast durchführt...
1
> TCP-Paket gesendet!
2
> 255.255.255.255:7899 -> 255.255.255.255:80 Syn: 1 Seq: 16777216 Ack: 0
und ich nicht weiss wieso!

Kann mir jemand sagen, wieso dieser Broadcast überhaupt durchgeführt 
wird? Das wäre aus meiner Sicht nämlich nicht nötig.

Anbei auch mal die Rückgabe von Wireshark, wobei ich die MAC-Adressen 
genullt habe.

Wireshark
---------
No.     Time        Source                Destination           Protocol 
Length Info
      1 0.000000    d1.1e.d1              ff.ff.ff              FC 
1500   Unknown frame (Bogus Fragment)

Frame 1: 1500 bytes on wire (12000 bits), 1500 bytes captured (12000 
bits)
    Arrival Time: Jun  3, 2015 23:50:38.217847000 CEST
    Epoch Time: 1433368238.217847000 seconds
    [Time delta from previous captured frame: 0.000000000 seconds]
    [Time delta from previous displayed frame: 0.000000000 seconds]
    [Time since reference or first frame: 0.000000000 seconds]
    Frame Number: 1
    Frame Length: 1500 bytes (12000 bits)
    Capture Length: 1500 bytes (12000 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    [Protocols in frame: eth:mdshdr:fc:data]
Ethernet II, Src: TwinhanT_71:00:00 (00:00:00:00:00:00), Dst: 
TwinhanT_71:3e:c1 (00:00:00:00:00:00)
    Destination: TwinhanT_71:3e:c1 (00:00:00:00:00:00)
        Address: TwinhanT_71:3e:c1 (00:00:00:00:00:00)
        .... ...0 .... .... .... .... = IG bit: Individual address 
(unicast)
        .... ..0. .... .... .... .... = LG bit: Globally unique address 
(factory default)
    Source: TwinhanT_71:00:00 (00:00:00:00:00:00)
        Address: TwinhanT_71:00:00 (00:00:00:00:00:00)
        .... ...0 .... .... .... .... = IG bit: Individual address 
(unicast)
        .... ..0. .... .... .... .... = LG bit: Globally unique address 
(factory default)
    Type: Unknown (0x0000)
MDS Header(Unknown(0)/Unknown(11))
    MDS Header
        ...0 1000 0000 0000 = Packet Len: 2048
        .... 0001 0000 00.. = Dst Index: 0x0040
        .... ..00 0000 0000 = Src Index: 0x0000
        .... 0000 0000 0000 = VSAN: 0
    MDS Trailer: Not Found
Fibre Channel
    [Exchange First In: 0]
    [Exchange Last In: 0]
    R_CTL: 0xff(0xf0/0xf)
    Dest Addr: ff.ff.ff
    CS_CTL: 0x1e
    Src Addr: d1.1e.d1
    Type: Ext Link Svc (0x01)
    F_CTL: 0x000000 Exchange Originator Seq Initiator CS_CTL
        0... .... .... .... .... .... = ExgRpd: Exchange Originator
        .0.. .... .... .... .... .... = SeqRec: Seq Initiator
        ..0. .... .... .... .... .... = ExgFst: NOT exchg first
        ...0 .... .... .... .... .... = ExgLst: NOT exchg last
        .... 0... .... .... .... .... = SeqLst: NOT seq last
        .... ..0. .... .... .... .... = Pri: CS_CTL
        .... ...0 .... .... .... .... = TSI: NOT transfer seq initiative
        .... .... 00.. .... .... .... = LDF: Last Data Frame - No Info 
(0x000000)
        .... .... ..00 .... .... .... = A01: no ack required (0x000000)
        .... .... .... ..0. .... .... = RetSeq: NOT retransmitted 
sequence
        .... .... .... .... ..00 .... = AA: ABTS - Cont (0x000000)
        .... .... .... .... .... 0... = RelOff: rel offset NOT set
    SEQ_ID: 0x00
    DF_CTL: 0x00
    SEQ_CNT: 0
    OX_ID: 0x5002
    RX_ID: 0x16d0
    Parameter: 0xdb6f0000
Data (1446 bytes)

von (prx) A. K. (prx)


Lesenswert?

Es ist nicht im Sinne des Erfinders, mittels RAW Sockets ein bestehendes 
Protokoll mit der bestehenden Protokollkennung (d.h. IP/TCP) zu 
überlagern. Selbst in jenen Fällen, wo das mal gemacht wird (ICMP) gibt 
es gewisse Nebeneffekte. Windows verhindert zudem aktiv, dass man TCP 
Frames über RAW Sockets nutzt, aus Sicherheitsgründen.

Du müsstest also eine andere Protokollkennung verwenden, damit kein 
Konflikt mit dem bestehenden Protokollstack entsteht.

von (prx) A. K. (prx)


Lesenswert?

Matthias schrieb:
> Fibre Channel

Was immer dieser Frame sein soll, du kannst ihn getrost als Sondermüll 
entsorgen. Schrott von A bis Z.

von Matthias (Gast)


Lesenswert?

> Windows verhindert zudem aktiv..,
Ich benutze Linux

> Es ist nicht im Sinne des Erfinders, mittels RAW Sockets ein bestehendes
> Protokoll mit der bestehenden Protokollkennung (d.h. IP/TCP) zu
> überlagern.

Das verstehe ich nicht. Dafür gibt es doch RAW und PACKET Sockets. Raw 
Sockets haben problemlos funktioniert. setsockopt und HDRINCL und dann 
weiss der Kernel, dass ich selbst Hand anlege.

Ähnlich müsste es doch auch mit PACKET Sockets gehen oder irre ich mich 
da?

> Du müsstest also eine andere Protokollkennung verwenden

Wenn Du schreibst, ich müsste eine andere Protokollkennung verwenden, 
welche wäre das denn dann? Ich will einen tcp/ip-Handshake initiieren 
und dafür wäre tcp/ip doch die richtige Protokollkennung oder welche 
sollte ich sonst dafür verwenden?

von (prx) A. K. (prx)


Lesenswert?

Matthias schrieb:
> und dafür wäre tcp/ip doch die richtige Protokollkennung oder welche
> sollte ich sonst dafür verwenden?

Auf beiden Seiten eine eigene. Immerhin willst du den kompletten 
Netzwerkstack oberhalb MAC|IP ersetzen. Woher soll der vorhandene Stack 
denn sonst wissen, dass nicht sein IP|TCP sondern deines gemeint ist?

: Bearbeitet durch User
von Matthias (Gast)


Lesenswert?

> Auf beiden Seiten eine eigene. Immerhin willst du den kompletten
> Netzwerkstack oberhalb MAC ersetzen.

Ja, genauer gesagt ab der MAC (ETH,IPH,TCPH)

> Auf beiden Seiten eine eigene.

Nein, nur auf der Seite des Absenders. Der Empfänger ist ein Apache auf 
meinem Localhost.

> Woher soll der vorhandene Stack
> denn sonst wissen, dass nicht sein IP/TCP sondern deines gemeint ist?

Ich habe gedacht, dass es ähnlich im Falle der Raw Sockets (setsockopt), 
entweder auch hier ein Äquivalent geben würde oder aber der Kernel, da 
ich PACKET Sockets verwende, direkt darüber informiert wird, dass ich 
dies von Hand tue. In der Literatur habe ich nichts darüber gefunden, 
dass bei PACKET Sockets oder genauer gesagt PF_PACKET i.V.m. RAW_SOCKETS 
ein Hinweis an den Kernel oder den Stack erfolgen müsste.

von Daniel A. (daniel-a)


Lesenswert?

Matthias schrieb:
> Einmal erhalte ich nicht die gewünschte Reaktion auf
> meinen Handshake-Versuch, so sehe ich bspw. gar keine tcp/ip-Daten in
> Wireshark. Darüber hinaus ist mir noch aufgefallen, dass mein Programm
> gar nicht, wie geplant, von 127.0.0.1 zu 127.0.0.1 sendet, sondern
> zuerst einmal einen Broadcast durchführt...
>
1
> TCP-Paket gesendet!
2
>> 255.255.255.255:7899 -> 255.255.255.255:80 Syn: 1 Seq: 16777216 Ack: 0
3
>
> und ich nicht weiss wieso!

Nun, in deinem uns unbekannten Programm machst du etwas grundlegendes 
falsch. Da hilft nur debuggen und RFCs lesen. Ausserdem solltest du 
schrittweise vorgehen, also zuerst das korrekte erstellen von eth frames 
DANACH ip und DANACH tcp. Du kannst hier den loopback 127.0.0.1 nicht 
verwenden, da du wlan0 und nicht das loopback device nutzt.
Du kannst keine IP deines Rechners wiederverwenden, weil dieser schon 
einen tcp stack besitzt. Wenn du den Standard für die Requirements 
gelesen hättest, wüsstest du, dass der bereits vorhandene stack ein RST 
senden muss.
http://tools.ietf.org/html/rfc1122#page-40

Lies auch folgendes durch:
http://tools.ietf.org/html/rfc793

Du kannst entweder deinen arp cache manipulieren, oder das arp Protokoll 
implementieren, um der Netzwerkschnittstelle eine zusätzliche ip zu 
verpassen, von der das OS nichts weiss Ich versuche gerade ebenfalls tcp 
in c mit packet sockets zu Implementieren. Bisher habe ich schon eth, 
arp, IPv4 und icmp implementiert. Falls interesse besteht, hier das 
Projekt:
https://github.com/Daniel-Abrecht/DPA-UCS
Leider fehlt dort die Doku noch.

von Matthias (Gast)


Lesenswert?

@ Daniel A.

> ...also zuerst das korrekte erstellen von eth frames
> DANACH ip und DANACH tcp

Das habe ich auch so gemacht. Ich habe einen char pointer erstellt, 
welcher der Größe der MTU für das Device entspricht. Danach wurden alle 
drei Header nacheinander darüber gecastet und im Anschluss erstelle ich 
erst den ether_header, dann den ip_header und danach den tcp_header bzw. 
setze deren Werte ein.

> ...wüsstest du, dass der bereits vorhandene stack ein RST
> senden muss.

Das hätte ich noch nachvollziehen können. Ich habe aber gar keine 
Antwort erhalten. Weil mein Programm statt der 127.0.0.1 eine 
Broadcast-Adresse verwendet hat, habe ich das Fehlen einer Bestätigung 
oder eines RSTs darauf zurück geführt.

Weil das in den Antworten angeklungen ist, vielleicht noch ein Hinweis. 
Sicher, ich habe in diverser Literatur und in Suchmaschinenergebnissen 
teilweise gelesen, dass bei meinem Vorhaben eigentlich der Stack im Wege 
steht.

Was mich dann aber doch bewogen hat es mit einem "einfachen" Client zu 
versuchen, welcher die drei Header -den PseudoHeader mal ausgenommen- 
direkt über das Netzwerkinterface sendet, war der Umstand, dass ich in 
Büchern Beispiele dafür gefunden habe, die selbiges mit arp oder icmp 
machen. Mir ist nicht klar, wieso das bei genannten Protokollen 
funktioniert oder zu funktionieren scheint.

Bei arp müsste der Kernel doch auch oder gerade dazwischenfunken.

Dein Projekt werde ich mir ansehen, vielen Dank dafür.

von Daniel A. (daniel-a)


Lesenswert?

Matthias schrieb:
> @ Daniel A.
>
>> ...also zuerst das korrekte erstellen von eth frames
>> DANACH ip und DANACH tcp
>
> Das habe ich auch so gemacht. Ich habe einen char pointer erstellt,
> welcher der Größe der MTU für das Device entspricht.

Die Formulierung ist hier noch etwas ungenau. Ein Pointer hat immer die 
gleiche grösse. Du wolltest vermutlich sagen, du hast einen 
Speicherbereich dessen Grösse der MTU entspricht reserviert (mit malloc, 
oder per array) und einen char pointer darauf zeigen lassen.

> Danach wurden alle
> drei Header nacheinander darüber gecastet

Man kann nicht "darüber" casten, man castet einfach, sprich wandelt 
einen Datentyp um. Du meintest vermutlich, du hast denn offset zum 
Anfang der Header zu deinem Charpointer addiert und in den jeweiligen 
structtyp, der diesen Hearer repräsentiert gecastet.

> und im Anschluss erstelle ich
> erst den ether_header, dann den ip_header und danach den tcp_header bzw.
> setze deren Werte ein.

Die reihenfolge, in der das Programm dies tut ist dem Empfänger egal. 
Mir geht es um die Reihenfolge in der du es Implementierst:

1) Also ip und tcp erstmal ignorieren.
2) Das Empfangen von Ethernet frames implementieren
3) Das empfangen Testen, kommen sinnvolle werte an, siehst du das selbe 
wie in wireshark, etc.
4) Das senden von Ethernet frames implementieren.
5) Das senden von Ethernet frames testen, zeigt wireshark die 
gewünschten werte an, etc.
6) Das Empfangen von IP frames implementieren
...

Also das Implementieren in einzelschritte aufteilen und diese sofort 
testen. Sofort alles implementieren zu wollen geht meistens schief.

>> ...wüsstest du, dass der bereits vorhandene stack ein RST
>> senden muss.
>
> Das hätte ich noch nachvollziehen können. Ich habe aber gar keine
> Antwort erhalten. Weil mein Programm statt der 127.0.0.1 eine
> Broadcast-Adresse verwendet hat, habe ich das Fehlen einer Bestätigung
> oder eines RSTs darauf zurück geführt.

Die Wireshark ausgabe sieht nicht so aus, als ob irgendein frame Korrekt 
wäre. Allerdings preferiere ich das Lesen eines Hexdumps statt der 
Wiresharkinterpretation.

> Beispiele dafür gefunden habe, die selbiges mit arp oder icmp
> machen. Mir ist nicht klar, wieso das bei genannten Protokollen
> funktioniert oder zu funktionieren scheint.
>
> Bei arp müsste der Kernel doch auch oder gerade dazwischenfunken.

Bei arp gibt es einen Request und eine Response. Eine arp-antwort ohne 
request wird verworfen. Die eigene Implementierung muss nur für eine 
noch nicht im Netzwerk vorhandene IP seine mac in einer arp Response 
senden.

ICMP und TCP basieren auf IP. Wenn der Systemeigener arp dienst eine IP 
nicht als seine eigene kennt, wird auf das IP packet nicht reagiert. 
Deine eigene Implementierung kann dann auf dieses Packet reagieren.

von Matthias (Gast)


Lesenswert?

Daniel A. schrieb:

> Die Formulierung ist hier noch etwas ungenau.
und
> Man kann nicht "darüber" casten,...

Du hast mich in beiden Punkten richtig verstanden und beides korrekt 
formuliert.

> 1) Also ip und tcp erstmal ignorieren.

Das ich die tcp- und ip-Header direkt miteingesetzt habe, hatte den 
einfachen Hintergrund, dass ich es für pragmatisch gehalten habe. Ich 
habe beides aus meinem vorherigen Programm mit dem RawSocket, welches 
einwandfrei funktioniert hat, übernommen. Die Bibliotheken waren einmal 
erstellt, aber viel wichtiger, die hierin enthaltenen Funktionen, welche 
die Header erstellt haben, sind gelaufen und haben das geliefert, was 
erwartet wurde.

> Die Wireshark-Ausgabe sieht nicht so aus, als ob irgendein frame Korrekt
> wäre.

Nein, wirklich nicht. Ich dachte, vielleicht sieht man anhand dessen, 
was Wireshark ausgegeben oder auch nicht ausgegeben hat, welcher 
Programm-Teil, welche spezielle Funktion oder dergleichen typisch für 
derartige, falsche Ausgaben ist.

Ich werde das Ganze jetzt noch einmal versuchen, erst einmal mit einem 
einzelnen, isolierten eth_hdr.

> Wenn der Systemeigener arp dienst eine IP
> nicht als seine eigene kennt, wird auf das IP packet nicht reagiert.
> Deine eigene Implementierung kann dann auf dieses Packet reagieren.

Verstehe ich das richtig, dass ich somit vermeide, dass mir der Stack 
des OS dazwischen funkt und ich so mein eigenes Paket offiziell 
erstellen kann? Einfach gesprochen...

von Daniel A. (daniel-a)


Lesenswert?

Matthias schrieb:
> Daniel A. schrieb:
>> Wenn der Systemeigener arp dienst eine IP
>> nicht als seine eigene kennt, wird auf das IP packet nicht reagiert.
>> Deine eigene Implementierung kann dann auf dieses Packet reagieren.
>
> Verstehe ich das richtig, dass ich somit vermeide, dass mir der Stack
> des OS dazwischen funkt und ich so mein eigenes Paket offiziell
> erstellen kann? Einfach gesprochen...

Ja, genau so ist es.

von Matthias (Gast)


Lesenswert?

Ich habe es ausprobiert, klappen tut es aber leider nicht.

Der von mir gesendete ARP-Reply ist in Ordnung, wird via Wireshark 
angezeigt, aber der ARP-Cache meines Desktop-Rechners bleibt vollkommen 
identisch.

Ich habe mal meine Suchmaschine angeworfen und bemerkt, dass es ähnliche 
Probleme immer in Verbindung mit einem bestimmten OS gab, nämlich Ubuntu 
12.04, dass benutze ich im Übrigen auch.

IP-Forwarding und dergleichen habe ich berücksichtigt, insofern scheiden 
diese schon Mal als Ursache aus, auf den ersten Blick jedenfalls.

Ein Tipp war, in den ARP-Logs nach etwaigen Fehlern zu suchen, wo aber 
sind ARP-Logs zu finden?? In meinem LOG-Verzeichnis finde ich nichts mit 
einem Bezug hierauf.

Frage, da der Ansatz aus mir unerfindlichen Gründen nicht funktioniert, 
kann ich nicht einfach unter '/etc/network/interfaces' eine statische IP 
hinzufügen und das Problem so umgehen?

Eine zweite Möglichkeit, welche mir eingefallen ist, was wäre, wenn ich 
einen Apache auf meinem Desktop-Rechner aufsetze und diesen mit DHCP 
ausstatte, so dass dieser mir einfach eine zweite IP zuweist?!

von Daniel A. (daniel-a)


Lesenswert?

Matthias schrieb:
> Der von mir gesendete ARP-Reply ist in Ordnung, wird via Wireshark
> angezeigt, aber der ARP-Cache meines Desktop-Rechners bleibt vollkommen
> identisch.

Davon würde ich mich gerne selbst überzeugen. Dazu sind dia Ausgabe von 
ifconfig, arp -a davor und danach, sowie ein Hexdump des ARP-Request und 
def ARP-Reply erforderlich.

> Ich habe mal meine Suchmaschine angeworfen und bemerkt, dass es ähnliche
> Probleme immer in Verbindung mit einem bestimmten OS gab, nämlich Ubuntu
> 12.04, dass benutze ich im Übrigen auch.

Ich verwende auch ubuntu, aber ich hatte damit keine Probleme.

> kann ich nicht einfach unter '/etc/network/interfaces' eine statische IP
> hinzufügen und das Problem so umgehen?

Nein, es gibt 2 andere Möglichkeiten. Aber man sollte die Uhrsache 
bekämpfen, nicht die Wirkung.

> Eine zweite Möglichkeit, welche mir eingefallen ist, was wäre, wenn ich
> einen Apache auf meinem Desktop-Rechner aufsetze und diesen mit DHCP
> ausstatte, so dass dieser mir einfach eine zweite IP zuweist?!

Das ist zwar möglich, löst aber keine Probleme und umgeht diese auch 
nicht.

PS: In Wireshark kann man von einem Packet einen Hexdump erzeugen, indem 
man mit der rechten Maustaste auf das Packet klickt und dann 
"Copy->Bytes->Hex Stream" anklickt.

: Bearbeitet durch User
von Matthias (Gast)


Lesenswert?

Die Ausgaben von ifconfig, arp -a und ip addr sind vor, wie auch nach 
dem reply, vollkommen identisch.

Das ist die Ausgabe des Wireshark-HexDumps eines gesendeten Reply 
Paketes.

xxxxxxxxxxxxffffffffffff08060001080006040002ffffffffffffc0a80266xxxxxxxx 
xxxxc0a80265

xxxxxxxxxxxx = empfaenger
ffffffffffff = spoof_sender

> ...sowie ein Hexdump des ARP-Request und def ARP-Reply erforderlich.

Ich habe gedacht, Du meintest Cache Poisoning und habe daher immer nur 
das Reply gesendet.

von Daniel A. (daniel-a)


Lesenswert?

Ein Reply ohne Request geht eben nicht. Wenn du korrekt auf ARP-Requests 
reagierst wird es funktionieren.

von Daniel A. (daniel-a)


Angehängte Dateien:

Lesenswert?

Ich habe gerade bemerkt, dass 50% aller Router und Switches alle 
Ethernetframes verwerfen, die die selbe Quell- und Ziel- mac adresse 
haben. Ich empfehle deshalb ein ethernet loopback device zu bauen, wie 
hier beschrieben:
https://www.juniper.net/documentation/en_US/junos14.2/topics/task/operational/fe-ge-loopback-plug-rj-45.html

Im Anhang ist ein Bild von meinem. Ist wirklich hilfreich.

Um es zu verwenden, einfach zuerst dem Ethernet-Interface eine ip 
zuweisen:
1
# beispiel
2
sudo ifconfig eth0 192.168.8.5/24
und danach das Device einstecken. Den Packetsocket an das 
Eternet-Interface binden, und mit dem arp-reply eine ip-addresse im 
selben subnet wie die ip des Eternet-Interface senden, aber nicht die 
selbe Adresse wie die des Eternet-Interface nutzen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.