pl: C Pointerei

sorry 4 Nerverei,

also ich hab da eine Funktion:

// Serialize Nameserver Query
int serNSQuery(char *query, char *qname, Sho type, Sho class){
    char *tok = ".";
    char *pch = NULL;
    pch = strtok(qname, tok);
    int i = 0;
    for( ; pch != NULL; pch = strtok(NULL, tok)){
        Oct len = strlen(pch);
        query[i] = len;
        memcpy(&query[i+1], pch, len);
        i += 1 + len;
    }
    query[i] = 0;     // Terminiern
    query[i+1] = 0;
    query[i+2] = 1;   // type A
    query[i+3] = 0;
    query[i+4] = 1;   // class IN
    int qlen = i + 5; // Länge der Query

    return qlen;
}

und damit ein Problem beim Aufruf. Setze ich

    char query[512];
    char qname[] = "example.org";
    int qlen = serNSQuery(query, qname, 1, 1);

funktioniert alles, wenn ich jedoch die Funktion mit serNSQuery(query, "example.org", 1, 1) aufrufe, schmiert die exe ab (es compliliert also). Was hab' ich nicht verstanden!?

Bitte mal um Hinweises, MfG

  1. Moin,

    also ich hab da eine Funktion:

    // Serialize Nameserver Query
    int serNSQuery(char *query, char *qname, Sho type, Sho class){
        char *tok = ".";
    

    … und du hast ein konstantes Token, das nicht als const deklariert ist:

    const char tok[] = ".";
    
        char *pch = NULL;
        pch = strtok(qname, tok);
    

    … Und du hast da eine Funktion, die man mit Vorsicht verwenden sollte …

        int i = 0;
    

    … sowie einen Array-Index eines vorzeichenbehafteten Datentyps

        for( ; pch != NULL; pch = strtok(NULL, tok)){
            Oct len = strlen(pch);
    

    … eine Umdefinition des Datentyps size_t oder einen möglichen Integer-Überlauf …

            query[i] = len;
            memcpy(&query[i+1], pch, len);
            i += 1 + len;
        }
        query[i] = 0;     // Terminiern
        query[i+1] = 0;
        query[i+2] = 1;   // type A
        query[i+3] = 0;
        query[i+4] = 1;   // class IN
        int qlen = i + 5; // Länge der Query
    
        return qlen;
    }
    
    

    und damit ein Problem beim Aufruf.

    Eins? Mehrere, siehe oben.

    Setze ich

        char query[512];
        char qname[] = "example.org";
        int qlen = serNSQuery(query, qname, 1, 1);
    

    funktioniert alles, wenn ich jedoch die Funktion mit serNSQuery(query, "example.org", 1, 1) aufrufe, schmiert die exe ab (es compliliert also). Was hab' ich nicht verstanden!?

    strtok

    Viele Grüße
    Robert

    1. Ahh, ja, für strtok() darf der String nicht konstant sein 😉

      Danke Dich!!

      1. Hallo pl,

        der entscheidende Satz aus der verlinkten Referenz:

        If such character was found, it is replaced by the null character '\0' and the pointer to the following character is stored in a static location for subsequent invocations.

        strtok modifiziert den übergebenen String. Man sollte also gut überlegen, ob man ihn im Urzustand noch braucht.

        Und die Story mit der static Location finde ich auch gefährlich - in einem Multithreading-Umfeld könnte das auf die Nase fallen. Aus meiner Sicht eine Funktion aus dem Gruselkabinett der 60er.

        Rolf

        --
        sumpsi - posui - clusi
        1. hi,

          der entscheidende Satz aus der verlinkten Referenz:

          If such character was found, it is replaced by the null character '\0' and the pointer to the following character is stored in a static location for subsequent invocations.

          strtok modifiziert den übergebenen String. Man sollte also gut überlegen, ob man ihn im Urzustand noch braucht.

          Genau. Das wusste ich schon vor 10 Jahren. Hatte ich nur vergessen.

          Und die Story mit der static Location finde ich auch gefährlich - in einem Multithreading-Umfeld könnte das auf die Nase fallen. Aus meiner Sicht eine Funktion aus dem Gruselkabinett der 60er.

          Mir gehts um die Serialisierung einer Anfrage an einen Nameserver

          Hast Du eine Altenative zu strtok() ?

          MfG

          PS: Was mich nervt ist, daß C den Little Endian bevorzugt. Für Netzwerkanwendungen also immer eine Um-Rechnerei 😉

          1. Hallo pl,

            Hast Du eine Altenative zu strtok() ?

            Hm. Ich muss erstmal verstehen was Du tust. Sieht so aus, als würdest Du einen String, der Punkte enthält, an den Punkten teilen. Und dann vor jedes Fragment die Länge setzen. Also

            "Hallo.Welt.Der.Zahlen" wird zu "\x05Hallo\x04Welt\x03Der\x06Zahlen".

            Richtig?

            In dem Fall würde ich an Position strlen(qname)+1 beginnen und rückwärts laufen. Zeichen werden gezählt und wenn Du einen Punkt in qname findest, schreibst Du nicht den Punkt, sondern den Zähler und setzt den Zähler auf 0. Wenn Du durch bist, schreibst Du den letzten Zählerstand auf query[0].

            Hab mal das hier ausprobiert:

            #include <stdio.h>
            #include <string.h>
            
            int main()
            {
                char query[512] = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
                //             0....:....1....:...
                char* qname = "forum.selfhtml.org";
                //            0....:....1....:...
                int p = strlen(qname)+1;
                char z = 0;
                for (int i=p-1; i>0; i--) {
                    char c = qname[i-1];
                    if (c == '.') {
                        c = z;
                        z = 0;
                    } else {
                        z++;
                    }
                    query[i] = c;
                }
                query[0] = z;
                query[p] = 0;
                query[p+1] = 0;
                query[p+2] = 1;
                query[p+3] = 0;
                query[p+4] = 1;
                printf("Result: p=%d, %s\nHex: ", p, query);
                for (int i=0; i<=p+4; i++) {
                    printf("%02x ", query[i]);
                }
                return 0;
            }
            

            PS: Was mich nervt ist, daß C den Little Endian bevorzugt.

            Tut es nicht. Dein Intel-Prozessor tut das. Nimm einen IBM Großrechner oder einen Motorola 68xxx Prozessor, die sind Big Endian.

            Rolf

            --
            sumpsi - posui - clusi
            1. moin,

              Hast Du eine Altenative zu strtok() ?

              Hm. Ich muss erstmal verstehen was Du tust. Sieht so aus, als würdest Du einen String, der Punkte enthält, an den Punkten teilen. Und dann vor jedes Fragment die Länge setzen. Also

              "Hallo.Welt.Der.Zahlen" wird zu "\x05Hallo\x04Welt\x03Der\x06Zahlen".

              Richtig?

              Korrekt. Nun, es gibt verschiedene Ansätze, in Perl sähe das übrigens so aus:

              # split into labels 
              # Serialize Nameserver Request
              sub packreq{
                  my $qname = shift; # example.org
                  my $type  = shift || 1; # A
                  my $class = shift || 1; # IN
                  return pack("(C/a*)*", (split /\./, $qname), "").pack("nn", $type, $class);
              }
              

              Und die Länge der Binary läßt sich am Ergebnis feststellen. Auch wenn mittendrin Nullbytes sind.

              PS: Was mich nervt ist, daß C den Little Endian bevorzugt.

              Tut es nicht. Dein Intel-Prozessor tut das. Nimm einen IBM Großrechner oder einen Motorola 68xxx Prozessor, die sind Big Endian.

              Stimmt natürlich. Aber am Perlcode siehst Du, wie einfach sich Perl damit tut, n ist der Short in Networkorder. Damit und mit den anderen Schablonen wirst Du von der Architektur unabhängig: Perlcode läuft plattformübergreifend.

              MfG

              PS: Zu Deinem Code: Die Länge muss als Byte in ein Socket geschrieben werden.

              1. Hallo pl,

                PS: Was mich nervt ist, daß C den Little Endian bevorzugt.

                Tut es nicht. Dein Intel-Prozessor tut das. Nimm einen IBM Großrechner oder einen Motorola 68xxx Prozessor, die sind Big Endian.

                Stimmt natürlich. Aber am Perlcode siehst Du, wie einfach sich Perl damit tut, n ist der Short in Networkorder. Damit und mit den anderen Schablonen wirst Du von der Architektur unabhängig: Perlcode läuft plattformübergreifend.

                Das kann C auch: Im Header arpa/inet.h findest du ntohs & Co, die für dich „das Denken übernehmen“: ntohs bspw. steht für network to host short, das heißt, dass der short-Wert (eigentlich uint16_t) anschließend definitv in der richtigen Host-Endianess vorliegt – auf IBM-PC kompatiblen Prozessoren findet daher ein Byte-Swapping statt, bei PowerPC zum Beispiel nicht.

                Viele Grüße
                Robert

                1. hi,

                  PS: Was mich nervt ist, daß C den Little Endian bevorzugt.

                  Tut es nicht. Dein Intel-Prozessor tut das. Nimm einen IBM Großrechner oder einen Motorola 68xxx Prozessor, die sind Big Endian.

                  Stimmt natürlich. Aber am Perlcode siehst Du, wie einfach sich Perl damit tut, n ist der Short in Networkorder. Damit und mit den anderen Schablonen wirst Du von der Architektur unabhängig: Perlcode läuft plattformübergreifend.

                  Das kann C auch: Im Header arpa/inet.h findest du ntohs & Co, die für dich „das Denken übernehmen“:

                  Ne, da muss ich schon selbst aktiv werden, das Denken nimmt mir da htons() gar nicht ab. Also den Code so schreiben, daß er entweder auf einer LE oder BE Architektur funktioniert. Oder mit Bitoperationen arbeiten so wie ich das hier gemacht habe. Also händisch in die Byteorder eingreifen.

                  MfG

                  1. Moin,

                    Das kann C auch: Im Header arpa/inet.h findest du ntohs & Co, die für dich „das Denken übernehmen“:

                    Ne, da muss ich schon selbst aktiv werden, das Denken nimmt mir da htons() gar nicht ab.

                    Doch.

                    Also den Code so schreiben, daß er entweder auf einer LE oder BE Architektur funktioniert.

                    Nein.

                    Oder mit Bitoperationen arbeiten so wie ich das hier gemacht habe. Also händisch in die Byteorder eingreifen.

                    Nein.

                    Beispiel ntohs und htons:

                    • ntohs stellt sicher, dass der in Network Byte Order vorliegende uint16_t-Wert anschließend in der für deinen aktuellen Host korrekten Byte Order vorliegt. Auf einem Big Endian-System muss dazu nichts weiter getan werden, der Wert wird durchgereicht, auf einem Little Endian-System findet ein Byte-Swapping statt. Ob du auf einem BE oder LE bist, braucht dich hier gar nicht zu kümmern, ntohs kümmert sich darum.
                    • htons arbeitet genau anders herum: Von deiner Host Byte Order wird in die Network Byte Order übersetzt (LE → BE) oder der Wert einfach durchgereicht (BE → BE).

                    Viele Grüße
                    Robert

                    1. Wir reden aneinander vorbei.

                      denn:

                      ntohs stellt sicher, dass der in Network Byte Order vorliegende uint16_t-Wert anschließend in der für deinen aktuellen Host korrekten Byte Order vorliegt.

                      Darum gehts doch gar nicht. Eine Netzwerkanwendung benötigt Zahlen in Networkorder (BE) und ob diese Endianness für den Host auf dem die Anwendung läuft zutrifft, ist völlig Wurst.

                      Sicher kann man einen int mit htons() oder htonl() in die Networkorder bringen aber auch nur wenn man sichergehen kann daß dieser int als LE vorliegt.

                      Deswegen schreibe ich ja auch auf meiner Seite wie man die Entwicklung von Netzwerk-Anwendungen mit Wireshark begleiten sollte, da werden Fehler dieser Art sofort sichtbar.

                      MfG

                      1. Moin,

                        Wir reden aneinander vorbei.

                        vollkommen korrekt, das zeigt deine Antwort:

                        ntohs stellt sicher, dass der in Network Byte Order vorliegende uint16_t-Wert anschließend in der für deinen aktuellen Host korrekten Byte Order vorliegt.

                        Darum gehts doch gar nicht. Eine Netzwerkanwendung benötigt Zahlen in Networkorder (BE) und ob diese Endianness für den Host auf dem die Anwendung läuft zutrifft, ist völlig Wurst.

                        Soweit korrekt.

                        Sicher kann man einen int mit htons() oder htonl() in die Networkorder bringen aber auch nur wenn man sichergehen kann daß dieser int als LE vorliegt.

                        Nein! Lies bitte noch einmal meinen Post und den Link auf die Manpage. ntohs stellt sicher, dass immer in die korrekte Host Byte Order konvertiert wird und htons immer korrekt in die Netzwerk Byte Order. Immer!

                        Viele Grüße
                        Robert

                        1. Ok, ich formuliere das Poblem mal anders:

                          Was nützt ein Datentyp dessen Endiannes systemabhängig ist? Gar nichts!

                          Genausowenig nützt dann solch ein struct:

                          typedef struct{
                              uint16_t id;
                              uint16_t flags;
                          
                              usw...
                          }DNSHeader;
                          

                          weil die Byteorder der Members systemabhängig sind.

                          Nein! Lies bitte noch einmal meinen Post und den Link auf die Manpage. ntohs stellt sicher, dass immer in die korrekte Host Byte Order konvertiert wird und htons immer korrekt in die Netzwerk Byte Order. Immer!

                          So!?

                          printf("%#x", htons(53) ); // 0x3500
                          

                          Rauskommen muss aber 0x0035 ! Wenn das Ergebnis richtig sein soll, muss der Entwickler wissen daß Port 53 als short LE die Zahl 13568 verkörpert.

                          MfG

                          1. Moin,

                            Was nützt ein Datentyp dessen Endiannes systemabhängig ist? Gar nichts!

                            Also sind Integer- und Double-Werte unnütz.

                            Nein! Lies bitte noch einmal meinen Post und den Link auf die Manpage. ntohs stellt sicher, dass immer in die korrekte Host Byte Order konvertiert wird und htons immer korrekt in die Netzwerk Byte Order. Immer!

                            So!?

                            printf("%#x", htons(53) ); // 0x3500
                            

                            53 ist BE 0x0035 und LE 0x3500 und das Format-Flag %x arbeitet mit der Endianess deines lokalen Systems. Es wäre doch auch sehr merkwürdig, wenn

                            printf("%#x\n", 53);
                            

                            0x3500 ausgäbe anstelle von 0x35. Wenn du überprüfen willst, ob htons richtig funktioniert, musst du dir das Bitmuster anschauen, z.B.

                            uint16_t ui = 53;
                            
                            fwrite(&ui, sizeof(ui), 1, file…);
                            /* … und die Ausgabedatei mit einem Hexeditor betrachten … */
                            
                            /* … oder mit einem anderen Blick darauf schauen: */
                            typedef union {
                                uint16_t i;
                                char b[2];
                            } Bytes;
                            
                            Bytes b;
                            b.i = ui;
                            
                            printf("0x%x%x", b[0], b[1]);
                            

                            Rauskommen muss aber 0x0035 ! Wenn das Ergebnis richtig sein soll, muss der Entwickler wissen daß Port 53 als short LE die Zahl 13568 verkörpert.

                            Nein, er muss seine C-Standard-Bibliothek richtig verwenden.

                            Viele Grüße
                            Robert

                            1. Gerade mal exerziert:

                              #include <stdio.h>
                              #include <stdint.h>
                              #ifdef WIN32
                              #include <Winsock2.h>
                              #else
                              #include <arpa/inet.h>
                              #endif
                              
                              const uint16_t ui = 53;
                              
                              int main(void) {
                                      const uint16_t iu = htons(ui);
                              
                                      printf("%#x %#x\n", ui, iu);
                                      fwrite(&ui, sizeof(ui), 1, stdout);
                                      fwrite(&iu, sizeof(iu), 1, stdout);
                              
                                      return 0;
                              }
                              
                              .\endian | xxd
                              
                              00000000: 3078 3335 2030 7833 3530 300d 0a35 0000  0x35 0x3500..5..  
                              00000010: 35                                       5
                              

                              (Ja, unter Windows gerade.) Aber schau dir mal den Teil nach dem Newline an: Da steht einmal 35 00, das ist deine 53 in Host Byte Order. Und das folgende 00 35 ist der in die Network Byte Order konvertierte Wert. Das funktioniert wie beschrieben.

                              Viele Grüße
                              Robert

                          2. Hallo pl,

                            
                            > printf("%#x", htons(53) ); // 0x3500
                            
                            

                            Rauskommen muss aber 0x0035

                            Hast Du das auf einer Big Endian Maschine ausgeführt? Dann müsste 0x0035 herauskommen. Auf eine LE-Maschine ist 0x3500 korrekt. Weil auf einer LE-Maschine 0x3500 als Bytefolge 0x00 0x35 gespeichert wird, was der BE-Bytefolge für 53 entspricht. Wie schon mehrfach gesagt: auf einer BE Maschine sind die hton-Funktionen No-Op Funktionen.

                            Die Idee ist doch, dass Du in deiner Software aus Effizienzgründen grundsätzlich mit den ints und der endianness arbeitest, die für die Ausführungsmaschine nativ sind. Nur dann, wenn Du eine definierte Endianness brauchst, konvertierst Du. D.h. genau am Rand deines Systems. Und die Implementierung von htons/ntohs ist so gemacht, dass Du nicht wissen musst, ob Du auf einer BE oder LE Maschine bist. Ruf einfach die Funktion auf. Und es passiert das richtige. Sozusagen Polymorphie in der C-Lib.

                            Rolf

                            --
                            sumpsi - posui - clusi
                            1. hi Rolf,

                              wenn ich in C die Zahl 53 als 16 Bit BE haben will, notiere ich 0x00 und 0x35 in 2 Oktetten und gut is. Und so muss ich in C mit einem DNS Header verfahren:

                              Sho h[6] = {
                                 htons(0xACDC),  // Identyfier
                                 htons(0x0100),  // Standard Query
                                 htons(0x0001),  // One Question
                                 0, 0, 0         // Answer RRs, Authority RRs, Additional RRs
                              };
                              

                              Weil ich weiß daß C intern ein 0xACDC als LE auffasst, also muss ich das rumdrehen. Mit Perl, was auf derselben Maschine läuft, ist eine solche Konvertierung nicht notwendig, wenn ich da $h[0] = 0xACDC; notiere kommt das mit derselben Byteorder am Server an.

                              Mit

                              printf("%#02x ", $_) for unpack("W*", pack L=>0x12345678);
                              

                              kannst Du übrigens testen, welche Architektur vorliegt, zum Ergebnis siehe perldoc -f pack

                              MfG

                              1. Hallo pl,

                                wenn ich in C die Zahl 53 als 16 Bit BE haben will, notiere ich 0x00 und 0x35 in 2 Oktetten und gut is.

                                Solange Du das als Konstante hast, klar.

                                Aber das ist ja selten der Fall. Wenn da ein Längenfeld ist, das BE gespeichert werden muss, sieht's wieder anders aus. Und dafür ist dann htons da.

                                Rolf

                                --
                                sumpsi - posui - clusi
                                1. hi Rolf,

                                  selbstverständlich tun htons() & Co. das was sie sollen.

                                  Das Problem ist jedoch, daß man einen 16 Bit BE in C nicht in einem uint16_t ablegen sollte wenn der Prozessor diesen uint16_t als LE auffasst. Nicht einmal mit einem dicken Kommentar dahinter würde ich das tun!

                                  MfG

                                  1. Moin,

                                    Das Problem ist jedoch, daß man einen 16 Bit BE in C nicht in einem uint16_t ablegen sollte wenn der Prozessor diesen uint16_t als LE auffasst. Nicht einmal mit einem dicken Kommentar dahinter würde ich das tun!

                                    dann erklär mir mal bitte, was daran falsch sein sollte:

                                    const uint16_t someNo = 42;
                                    uint16_t someNwNo = htons(someNo);    /* someNo in network byte order */
                                    
                                    printf("Writing to network something #%u\n", someNo);
                                    write(sockh, &someNwNo, 2);
                                    

                                    Viele Grüße
                                    Robert

                                    1. Alles gut @Robert B.

                                      solange Du ein htons(mynumber); unmittelbar vor der Ausgabe notierst, kann das auch jeder nachvollziehen. Was ich meinte ist, daß man ein uint16_t host = htons(mynumber); nicht machen sollte!

                                      MfG