Best-practise Frage zum Thema DNS Lookup



  • Bisherige Situation ist folgende:

    Einer meiner Funktionen kann man einen String, der einen Hostname enthält, und seine Länge als Parameter übergeben (da sehr oft mit nicht-nullterminierten Strings gearbeitet wird, ergibt das schon Sinn, und nein, verzichten kann man nicht direkt darauf, ohne einen Haufen weiteren Bloat-Codes zu erzeugen), und daraufhin versucht diese Funktion, eine Verbindung zu erstellen. Die Strings können folgendes Format aufweisen:

    http://example.org
    example.org:80
    https://example.org:80 (Portangabe ist wichtiger als Standardport des Protokolls, es wird 80 genommen)
    http:127.0.0.1:80 (lokaler Server, Angabe durch IP)
    

    Diese Funktion (nennen wir sie sock_connect) übernimmt im Hintergrund einen ganzen Hauen anderer Parameter, beispielsweise die Socket-Familie (AF_INET, AF_INET6, etc ...) und den Socket-Typ (SOCK_STREAM, SOCK_DGRAM, etc ... und ja, da gibt es noch weitere). Das Protokoll wird derzeit noch aus dem Socket-Typen abgeleitet (STREAM gehört TCP, DGRAM gehört UDP, aber das wird eventuell noch geändert). Der normale Caller merkt davon nichts, er ruft nur leicht zu merkende Makros auf, die die entsprechenden Parameter an die Funktion übergeben (sock_connect_v4_tcp() beispielsweise).

    Die Funktion prüft mit Hilfe einer Parsing-Funktion (die auf URIs spezialisiert ist), ob aus dem String eine IP zu extrahieren ist, und weicher Adress-Familie diese zugeordnet werden kann, und ob irgendwie ein Port (notfalls über Standardportzuweisung durch das Protokoll) zugewiesen werden kann. Wenn eine IP gefunden werden kann, wird der DNS-Lookup überprungen, es wird ein Socket mit dem angegebenen Socket-Typ und -Protokoll erstellt, und die Verbindung wird (über connect(2)) versucht. Wenn KEINE IP gefunden werden kann, wird eine eigene DNS-Funktion (nennen wir sie dns_get_first) aufgerufen, die sicherstellt, dass für den Aufruf von gethostaddr der Hostnamestring nullterminiert ist, und nur einen Eintrag in den Ausgabespeicher der Funktion schreibt. Dann wird wieder die Verbindung versucht.

    Soweit, so kompliziert. Jetzt hatte ich mir überlegt, die Logik des Prüfens auf eine IP-Adresse von sock_connect nach dns_get_first zu verschieben, mit der folgenden Begründung: dns_get_first hat bisher IMMER einen DNS-Lookup gemacht, selbst dann, wenn eine IP-Adresse im Hostnamen herauszulesen war. Weswegen man überall sonst selbst eine Prüfung auf eine solche Adresse machen musste. Stattdessen soll dns_get_first jetzt garantieren, dass wenn eine IP-Adresse, die auch sonst den Vorgaben (die man wie bei sock_connect, mit der Socket-Familie und dem Socket-Typen) übereinstimmt, gefunden wurde, dass man sich den DNS-Lookup jetzt spart und lieber direkt manuell eine Socket-Adresse konstruiert und in den Ausgabespeicher schreibt. Zusätzlich entfällt dann im jedem Caller der Funktion die Notwendigkeit, selbst die Prüfung zu machen.

    Hier ist das Problem: socks_connect merkt sofort, dass etwas nicht stimmt, und sendet daher einen Fehlercode an den Caller, wenn die Verbindung nicht funktioniert - was zum Beispiel dadurch sein kann, dass der Nutzer 127.0.0.1, AF_INET und DGRAM angegeben hat, während der Server unter ::1, AF_INET6 und STREAM erreichbar ist. Aber das wäre nicht meine Verantwortung, wenn der Code nicht funktioniert - der Nutzer hat ihn direkt (über die Makros aufgerufen).
    Beim DNS-Code würde der Nutzer sich eher darauf verlassen, dass ein neuer DNS-Request gemacht wird, selbst dann, wenn eine IP als Hostname angegbeen wurde.

    Was würdet ihr machen bzw. gibt es etwas, dass ich übersehen habe, was man intelligenter lösen könnte? Verwendete Programmiersprache ist C, aber es geht hier um die Logik, nicht um Features der Programmiersprache.



  • Ich würde nicht dns_get_first ändern sondern eine weitere Funktion machen - könnte man z.B. resolve_first oder parse_direct_or_dns_get_first oder so nennen.
    Diese würde dann gucken ob's direkt eine IP Adresse ist und diese ggf. parsen und zurückliefern, bzw. wenn nicht einfach ein return dns_get_first(params) machen.

    Das ist eine 100% saubere Erweiterung die erstmal gar nix bewirkt.
    Danach kannst du dann wo du möchtest den den duplizierten Code durch einen Aufruf der neuen Funktion ersetzen.

    (Bzw. ich würde vermutlich sogar zwei neue funktionen machen. Eine die nur versucht zu parsen, und wenn das fehlschlägt einfach nur "leider nicht" zurückliefert. Und dann die oben erwähnte Funktion mit Hilfe der neuen parse_direct Funktion implementieren.)

    Socketprogrammierer schrieb:

    Hier ist das Problem: socks_connect merkt sofort, dass etwas nicht stimmt, und sendet daher einen Fehlercode an den Caller, wenn die Verbindung nicht funktioniert - was zum Beispiel dadurch sein kann, dass der Nutzer 127.0.0.1, AF_INET und DGRAM angegeben hat, während der Server unter ::1, AF_INET6 und STREAM erreichbar ist. Aber das wäre nicht meine Verantwortung, wenn der Code nicht funktioniert - der Nutzer hat ihn direkt (über die Makros aufgerufen).
    Beim DNS-Code würde der Nutzer sich eher darauf verlassen, dass ein neuer DNS-Request gemacht wird, selbst dann, wenn eine IP als Hostname angegbeen wurde.

    Was würdet ihr machen bzw. gibt es etwas, dass ich übersehen habe, was man intelligenter lösen könnte?

    Dieser Teil liest sich jetzt irgendwie so als ob du da irgend ein Problem siehst. Ich verstehe aber nicht welches.

    Bzw. falls es nur um den Teil "beim DNS-Code würde der Nutzer sich eher darauf verlassen, dass ein neuer DNS-Request gemacht wird" geht, das bliebe in der von mir vorgeschlagenen Variante ja weiterhin so. D.h. Client Code könnte immer den garantierten DNS Lookup bekommen wenn Client Code das will.



  • hustbaer schrieb:

    (Bzw. ich würde vermutlich sogar zwei neue funktionen machen. Eine die nur versucht zu parsen, und wenn das fehlschlägt einfach nur "leider nicht" zurückliefert. Und dann die oben erwähnte Funktion mit Hilfe der neuen parse_direct Funktion implementieren.)

    int dns_get_first_optional_parsing(/*bla*/)
    {
            int error_code;
            uri_object host_info;
    
            /*Prüfe, ob IP geladen werden konnte - bei != 0 gibt es immer einen Fehler.*/
            if(!uri_parse(&host_info,hostname,hostname_length))
            {
                    /*Konnte geparst werden, Informationen zuweisen und dann zurückkehren*/
                    (*output_sockaddr) = host_info.ip_address_info;
                    return 0;
            }
    
            /*DNS-Request*/
            if((error_code = dns_get_first(/*bla*/)))
            {
                    return error_code;
            }
    
            /*Keine IP, konnte aber durch DNS aufgelöst werden.*/
            return 0;
    }
    

    Meinst du das so?

    hustbaer schrieb:

    Dieser Teil liest sich jetzt irgendwie so als ob du da irgend ein Problem siehst. Ich verstehe aber nicht welches.

    Beispiel: Caller möchte eine SOCKS-Verbindung aufmachen, gibt an: "127.0.0.1:9050", AF_INET, aber macht aus irgendeinem Grund statt eine TCP- eine UDP-Verbindung. Durch einen DNS-Lookup käme heraus, dass UDP gar nicht unterstützt werden würde (getaddrinfo liefert da jeweils andere Ergebnisse, zumindest, als ich es getestet habe). Da ich den Lookup aber nicht mache, würde mir keine Fehlermeldung zurückkommen, sondern ich würde in der Funktion die Socket-Adresse bauen, und die Funktion gibt keinen Fehler zurück. Der Caller speichert das Ergebnis irgendwo zwischen - sagen wir mal, in einen Speicher, der für verschiedene Threads (nur Read-Only) verwendet wird. Und wundert sich später, warum der DNS-Lookup funktioniert, aber das Verbinden nicht.

    Im Gegensatz dazu die sock_connect-Funtion - Verzichtet auf den DNS-Lookup, baut die Adresse lokal, aber verbindet sich direkt und wirft eine Fehlermeldung. Da weiß der Caller schon sehr viel eher, was Sache ist, und kann direkt beim Call ansetzen.

    Vielleicht bin ich auch nur Paranoid - aber wenn man für sowas schon ein API zusammenbaut, dann auch bitte richtig. Das soll schließlich noch in Jahren stehen.

    hustbaer schrieb:

    Bzw. falls es nur um den Teil "beim DNS-Code würde der Nutzer sich eher darauf verlassen, dass ein neuer DNS-Request gemacht wird" geht, das bliebe in der von mir vorgeschlagenen Variante ja weiterhin so. D.h. Client Code könnte immer den garantierten DNS Lookup bekommen wenn Client Code das will.

    Da hast du recht - am Interface würde sich nichts ändern, nur am Funktionsnamen.


Anmelden zum Antworten