GET-Request in C funktioniert nicht bei allen Domains



  • Hallo,

    ich versuche einen Fehler in meinem kleinen Socket-Programm zu finden. Ich habe mir die IP-Adresse von wikipedia.org besorgt und versuche nun einen HTTP-Request zu senden. Bei anderen Seiten funktioniert das, bei wikipedia.org aber beispielsweise nicht.

    Ich habe mir mal mit Wireshark angeschaut was mein Programm so verschickt und was telnet so verschickt und mir ist aufgefallen, dass telnet ebenso wie mein Programm ein Paket im TCP-Protokoll verschickt in dem der GET-Request vorhanden ist. Aber zusätzlich verschickt telnet noch ein Paket mit dem HTTP-Protokoll mit dem GET-Request. (Siehe: http://www.pic-upload.de/view-25092635/wireshark.png.html)

    Ich würde also fast behaupten, damit alle Server das akzeptieren, muss ich den Request noch einmal schicken, diesmal auf der richtigen Schicht. Die Frage die ich mir stelle, wozu ich leider keine passende Anleitung gefunden habe ist: Wie mache ich das? Oder irre ich mich hier nun?

    Mein aktueller Code:

    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int main(void)
    {
      int sockfd;
      int connst;
      int rw;
      struct sockaddr_in addr;
      char buf[BUFSIZ];
    
      memset(&addr, 0, sizeof(struct sockaddr_in));
      addr.sin_family = AF_INET;
      addr.sin_port = htons(80);
      inet_pton(AF_INET, "91.198.174.192", &addr.sin_addr);
    
      sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
      connst = connect(sockfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in));
    
      if(connst == -1)
      {
        puts(strerror(errno));
      }
    
      rw = write(sockfd, "GET /\r\n", strlen("GET /\r\n"));
      printf("wrote %d\n", rw);
    
      bzero(buf, BUFSIZ);
      rw = read(sockfd, buf, BUFSIZ-1);
      printf("read %d\n", rw);
      puts(buf);
    
      close(sockfd);
    
      return 0;
    }
    

    Kompiliert mit: $ gcc -Wall -Wextra -pedantic

    Ich wäre für Korrekturen, Lösungen und eventuelle Tutorial/Buchempfehlungen offen. Grundsätzlich wäre ich aber sehr dankbar, wenn ihr mir helfen würdet, das Beispiel ans Laufen zu bekommen. 🙂

    Danke und beste Grüße



  • Das wundert mich nicht.
    Da gehören noch ein paar Headerfields dazu. Minimum ist Host zum Beispiel. Ansonsten wird der Request bei vielen Servern mit einem schliessen der Verbindung quittiert.
    Auch sollte die HTTP-Version angegeben werden.

    Im HTTP 1.0 wird der request durch schliessen der Verbindung nach der Antwort beendet. In Http 1.1 kann die Verbindung offen bleiben.



  • Das dachte ich mir auch, aber ich habe mit telnet genau das gleiche gemacht:

    $ telnet 91.198.174.192 80
    // hier die Üblichen Ausgaben
    GET /

    und dann zwei mal Enter. Das funktioniert. (Ich weiß das nicht die schönste Art, aber mich wundert es, dass es in telnet geht und bei mir nicht. 🙄



  • Du sagst es schon: ZWEI mal Enter. In Deinem C-Programm schickst Du aber nur ein \r\n. Außerdem fehlt das Feld mit der Protokollversion. Wobei der Wikipedia-Server das ignoriert. Korrekt wäre:

    GET / HTTP/1.0\r\n\r\n
    

    Oder besser:

    GET / HTTP/1.1\r\nHost: de.wikipedia.org\r\nConnection: close\r\n\r\n
    

    Ansonsten ist Dein Programm doch sehr rudimentär. Aber das weißt Du sicher. Liefert connect -1 zurück, wird eine Fehlermeldung ausgegeben und dann als nächstes auf den Filedescriptor -1 geschrieben. Das fällt mir so direkt ins Auge. In einem "richtigen" Programm solltest Du auch nicht die IP-Adresse "besorgen", sondern mit entsprechenden Funktionen wie getaddrinfo die Auflösung des Hostnamens dem Programm überlassen.



  • Hmm... Sehr interessant. Ich habe es nun geändert und es klappt tatsächlich. Die entsprechende Zeile ist nun:

    rw = write(sockfd, "GET / HTTP/1.0\r\n\r\n", strlen("GET / HTTP/1.0\r\n\r\n"));
    

    Um es noch einmal zusammenzufassen:
    Mein Programm machte (in der Urfassung) den TCP Handshake und anschließend wurde ein TCP-Paket verschickt mit dem GET-Request, wie hier zu sehen: http://www.pic-upload.de/view-25105297/wireshark2.png.html

    Telnet hatte ein ähnliches Verhalten. Erst den TCP-Handshake, dann an sich das exakt gleiche Paket (also vom Inhalt her, nur GET / mit nur einem Zeilenumbruch) und anschließend schickte telnet ein weiteres Paket auf dem HTTP-Protokoll, das hier: http://www.pic-upload.de/view-25092635/wireshark.png.html

    Und hier unterschieden sich die zwei Versionen. Nun verschickt mein Programm, mit dem doppelten Zeilenumbruch ein Paket und bekommt ohne Probleme eine Antwort, verzichtet aber auf das "erste" Paket, was die Urfassung nur verschickte.

    Aber das heißt letztlich doch, das telnet das Paket selbst korrigiert, oder sehe ich das falsch? Abgesehen davon dachte ich, dass ein Zeilenumbruch ausreicht, da RFC 1945 den Simple-Request wie folgt definiert:

    Simple-Request = "GET" SP Request-URI CRLF

    (Quelle: https://tools.ietf.org/html/rfc1945#page-23)
    Hier ist eben nur ein CRLF drin, daher nahm ich an, dass das ausreicht.



  • Nach dem Request werden Header-Fields erwartet die mit einer Zeile nur bestehend aus CRLF beendet werden. Danach kommt die Payload.

    Telnet schickt die eingegebenen Daten bei jedem newline explizit auf die Leitung. Daher kommt erst ein Paket mit den Daten und dem ersten CRLF und dann ein weiteres Paket mit dem leeren Text und ein CRLF.

    Im TCP/IP Protokoll ist eine beliebige Fragmentierung der Daten möglich. Man kann auch Byteweise senden. Das würde dann eben für jedes Zeichen ein Frame geben.

    Bei einem asynchronen Empfang der Daten muss der Empfänger immer mit einer partiellen Übertragung rechnen.



  • Ah, das erklärt einiges! Vielen Dank für die Antworten! 🙂


Anmelden zum Antworten