Markus**: DNS Anfragen - Verständnisproblem?!

Hallo Forum,

ich möchte mal behaupten, dass ich die Funktion des Ethernet einigermaßen gut verstanden habe. Da habe ich interessehalber mal ein DNS-Relay programmiert; IP, UDP und DNS protokoll zum leichteren manipulieren als Klassen implementiert und meinen eigenen Rechner im IP-v4 Protokoll als DNS-Server angegeben.
Frage ich nun forum.de.selfhtml.org an, geht die Frage wie erwartet auf Port 53 an mein Programm. Hier baue ich das abgegriffene IP-Paket um, indem ich als Ziel-Adresse den wirklichen DNS Server angebe und dem Quellport 10 aufaddiere.
Wie erwartet erhalte ich eine Antwort auf [altes Paket]:Quellport + 10
hier baue ich das Paket abermals um und setze es wieder ab.
Klappt bestens und ich bin in der Lage den DNS-Verkehr zu ristriktieren, bzw. an Uhrzeiten zu koppeln (habe einen 9jährigen Sohn) ;)

Allerdings ist mir das mit dem port +10/-10 nicht geheuer und ich hab im Moment keine ahnung wie ich eine freie Portnummer ermitteln kann. könnte ja mal sein, dass auf port n+10 schon ein anderer Dienst rumkriecht.

Was passiert dann?

Gruß, MHI

  1. Yerf!

    Allerdings ist mir das mit dem port +10/-10 nicht geheuer und ich hab im Moment keine ahnung wie ich eine freie Portnummer ermitteln kann. könnte ja mal sein, dass auf port n+10 schon ein anderer Dienst rumkriecht.

    Was passiert dann?

    Wenn ich mich richtig erinnere kommt man sich erst in die Quere, wenn auch die Zieldaresse + Port identisch ist, da alle 4 Werte die Verindung identifizieren.

    Allerdings ist bei einer ausgehenden Verbindung der Port ja egal. Deshalb gibt es auch Methoden die einem einen beliebigen Port dafür geben. Mein Wissen darüber ist aber schon etwas eingerostet... den genauen Namen der API-Funktion weiß ich nimmer (und wenn, dann wäre das möglicherweise Linux-Spezifisch)

    Gruß,

    Harlequin

    --
    RIP --- XHTML 2
    nur die Besten sterben jung
    1. Yo!

      Yerf!

      Wenn ich mich richtig erinnere kommt man sich erst in die Quere, wenn auch die Zieldaresse + Port identisch ist, da alle 4 Werte die Verindung identifizieren.

      Das sehe ich grundsätzlich genauso. Da ich nochdazu schon auf IP-Ebene in das Protokoll eingreife hab ich da auch eher kein Problem mit. Das würde ja wenn überhaupt auch nur zu kollisionen kommen wenn das Programm lokal läuft. Trotzdem erscheint mir das mit dem Port +10/-10 nicht grade als elegante Lösung vor. Ist halt "Dirty" da ich nichtmal abchecke ob Port+10 > 65535 ist! ;)

      Allerdings ist bei einer ausgehenden Verbindung der Port ja egal. Deshalb gibt es auch Methoden die einem einen beliebigen Port dafür geben. Mein Wissen darüber ist aber schon etwas eingerostet... den genauen Namen der API-Funktion weiß ich nimmer (und wenn, dann wäre das möglicherweise Linux-Spezifisch)

      Vielen Dank für Deine Einschätzung,

      Gruß, Markus**

  2. Hi!

    Wie erwartet erhalte ich eine Antwort auf [altes Paket]:Quellport + 10
    Allerdings ist mir das mit dem port +10/-10 nicht geheuer und ich hab im Moment keine ahnung wie ich eine freie Portnummer ermitteln kann.

    Und wie greifst du die Antwort dort ab? Doch sicher mit einem Binding auf diesen Port. Wenn das nicht klappt, ist er belegt.

    Lo!

    1. Moin

      Und wie greifst du die Antwort dort ab? Doch sicher mit einem Binding auf diesen Port. Wenn das nicht klappt, ist er belegt.

      Wie schon gesagt, gehe ich schon in Layer3 auf IP ran.

      Lo!

      1. Hi!

        Und wie greifst du die Antwort dort ab? Doch sicher mit einem Binding auf diesen Port. Wenn das nicht klappt, ist er belegt.
        Wie schon gesagt, gehe ich schon in Layer3 auf IP ran.

        Machst du TCP oder UDP? Bei TCP gibst du eigentlich gar keinen Quellport an, einen freien sucht sich dann der TCP/IP-Stack von selbst. UDP ist ja eine Einbahnstraße, da musst du für die zurückkommenden Pakete einen Listener aufsetzen, der bindet sich an einen Port - aber nur, wenn er frei ist.

        Lo!

        1. Hi!

          Und wie greifst du die Antwort dort ab? Doch sicher mit einem Binding auf diesen Port. Wenn das nicht klappt, ist er belegt.
          Wie schon gesagt, gehe ich schon in Layer3 auf IP ran.

          Machst du TCP oder UDP? Bei TCP gibst du eigentlich gar keinen Quellport an, einen freien sucht sich dann der TCP/IP-Stack von selbst. UDP ist ja eine Einbahnstraße, da musst du für die zurückkommenden Pakete einen Listener aufsetzen, der bindet sich an einen Port - aber nur, wenn er frei ist.

          ich nutze, wie es üblich ist UDP für's DNS.
          Da ich ja, wie gesagt das IP abhöre gibt's da nichts mit Ports. Ich gucke mir das UDP Paket welches in meinem IP-Paket gekapselt ist an und schaue mir die Portnummern (Source & Dest) an. IP's entnehme ich dem IP Header. So kann ich eine Zuordnung machen.
          Dann manipuliere ich die Pakete entsprechend und setze sie wiederum auf IP ab.
          Dass UPD ein Verbindungsloses Protokoll ist, ist mir bewusst. Allerdings ist mir im Laufe der Diskussion hier auch bewußt geworden, dass ich im Grunde alle Anfragen cachen muß um feststellen zu können an welche Adresse:Port kombination ich die Antwort weitergeben muß.
          Lokal ist das ja "wumpe" da die Ziel IP für die Antwort immer die lokale IP ist, kämen aber Anfragen von anderen Rechnern aus dem Netz, habe ich hier keine Möglichkeit die Antworten zuzuordnen.

          Lo!

          Vielen Dank für das Gespräch, es hat mir weitergeholfen! ;)

          Gruß, Markus**

          1. Hi!

            ich nutze, wie es üblich ist UDP für's DNS.
            Da ich ja, wie gesagt das IP abhöre gibt's da nichts mit Ports.

            Warum machst du das auf der Ebene und nicht mindestens eine Schicht weiter oben? Und wie machst du das, welches Programmiersprache, welche Funktionen?

            Ich gucke mir das UDP Paket welches in meinem IP-Paket gekapselt ist an und schaue mir die Portnummern (Source & Dest) an. IP's entnehme ich dem IP Header. So kann ich eine Zuordnung machen. Dann manipuliere ich die Pakete entsprechend und setze sie wiederum auf IP ab.

            Du hast bei UDP keine andere Chance, belegte Ports festzustellen, als ihn selbst zu binden zu versuchen. Ein Test-Paket hinsenden ist nicht, weil da nichts zurückkommt, wenn nicht gerade die lauschende Anwendung aus welchen Gründen auch immer eine Antwort schickt, was sie nicht muss. Sie muss auch nicht zwingend wissen, wohin sie antworten müsste, weil bei UDP der Quellport im Prinzip irrelevant ist und auf 0 gesetzt werden kann. Bestimmte Protokolle können das vielleicht anderes handhaben, aber UDP ist nunmal eine Einwegveranstaltung und da ist theoretisch der Absenderport nicht relevant.

            Du könntest mit netstat auf der Kommandozeile eine Liste der belegten Ports abfragen. Da gibts sicher auch ein programmatisches Pendant dazu. Doch diese Liste ist eine Momentaufnahme und eigentlich unbrauchbar für deinen Fall, weil zwischen Abfragen und Antwort jemand anderes sich den Port geschnappt haben kann. Die einzige Chance ist, sich auf den Port zu binden zu versuchen und dann auch dort zu bleiben, bis die Kommunikation beendet ist.

            Wenn du aber auf der IP-Ebene hantierst, was interessiert dich dann, ob ein Port belegt ist? Du fängst doch das Paket ab, wenn es deins ist und gibt es nicht weiter.

            Lo!

            1. Hi!

              Jo, Mahlzeit!

              Wenn du aber auf der IP-Ebene hantierst, was interessiert dich dann, ob ein Port belegt ist? Du fängst doch das Paket ab, wenn es deins ist und gibt es nicht weiter.

              ich nutze c# und poste nun hier mal den relvanten Code:

                
              if (pk.innerProtocol is UDP)  
              {  
              	if (pk.innerProtocol.DestinationPort == 53)  
              	{  
              		//ask MainDNS Server  
              		pk.innerProtocol.SourcePort += 10; //Port, den die Anwendung blockiert +10  
              		pk.DestinationAdress = new IPAdress("{DNS-Server IP}");  
              		m.SendTo(pk.Data, (EndPoint)(new IPEndPoint(System.Net.IPAddress.Parse("1.0.0.0"), 0))); //SendTo erwartet einen Endpunkt; Wird aber ohnehin irgnoriert, da das IP-Paket komplett abgesetzt wird.  
              	}  
              	else if ((pk.innerProtocol.SourcePort == 53) && (pk.SourceAdress.StringIP.Equals("{DNS-Server IP}")))  
              	{  
              		//answer to Application  
              		pk.innerProtocol.DestinationPort -= 10; //Port -10 um das Paket dem Port andem die Anwendung lauscht zuzustellen  
              		pk.SourceAdress = new IPAdress("{Lokale IP}"); //Source IP zurücksetzen damit das Paket "valide" bei der Anwendung ankommt.  
              		m.SendTo(pk.Data, new IPEndPoint(System.Net.IPAddress.Parse("1.0.0.0"), 0));  
              	}  
              }  
              
              

              Wenn ich also den Port nicht incrementiere, sendet der DNS Server die Antwort direkt an die Anwendung zurück, die ja an dem Port lauscht. Die Anwendung verwirft es, weil die Absende IP-Adresse nicht stimmt; ist ja in dem Fall die vom DNS und nicht meine lokale.
              Problematisch ist halt auch, dass die IP der anfragenden Anwendug die gleiche ist, wie die meines Sockets.

              Lo!

              Ho!

              1. Hi!

                ich nutze c# und poste nun hier mal den relvanten Code:

                Ok, das ist aber nur die halbe Wahrheit. Ich sehe, dass du mit IPAdress und IPEndPoint ein paar Verwaltungsstrukturen füllst. Das eigentlich interessante ist m.SendTo(), aber welcher Klasse m angehört, ist nicht zu sehen. Wenn ich raten soll, wird m ein Objekt der Klasse System.Net.Sockets.Socket sein. Da wäre interessant, wie du m instantiierst und ob irgendwelche weiteren Parameter gesetzt werden (nebst wichtigen Methodenaufrufen wie Bind und Listen), sowie welche (Art) Werte du da übergibst. SentTo() ist jedoch nur für das Senden zuständig. Was aber tust du, um die zu empfangenden Daten entgegennehmen zu können? Ist das ein weiteres Socket-Objekt? Du kannst ja auch noch was zu pk sagen, aber das scheint mir für das Verständnis erstmal weniger relevant (fürs erste jedenfalls).

                Wenn ich also den Port nicht incrementiere, sendet der DNS Server die Antwort direkt an die Anwendung zurück, die ja an dem Port lauscht.

                Aha, nun lauscht dein Programm also doch an einem Port. Das kann es nicht, wenn es sich nicht an ihn bindet, was nur erfolgreich ist, wenn die Kombination aus Port- und IP-Adresse frei ist.

                Problematisch ist halt auch, dass die IP der anfragenden Anwendug die gleiche ist, wie die meines Sockets.

                Inwiefern? Der DNS-Request geht doch an deine Anwendung, die dafür auf dem Port 53 lauschen muss. Andere Server bekommen doch davon nichts mit.

                Ich würde ja den weiterzureichenden DNS-Request über die Namensauflösungsfunktion des Rechners laufen lassen. Das geht aber nicht, wenn es dasselbe System ist, auf dem das Programm läuft und dein Nachwuchs surfen will, da ja auf diesem die Systemeinstellung für DNS auf localhost zeigt und damit sämtliche Namensauflösungen an den gesendet werden, womit man sich im Kreis dreht. Wenn es zwei Rechner sind, ist es jedoch kein Problem.

                Lo!

                1. Hi!

                  ich nutze c# und poste nun hier mal den relvanten Code:

                  Ok, das ist aber nur die halbe Wahrheit. Ich sehe, dass du mit IPAdress und IPEndPoint ein paar Verwaltungsstrukturen füllst. Das eigentlich interessante ist m.SendTo(), aber welcher Klasse m angehört, ist nicht zu sehen.....
                  Aha, nun lauscht dein Programm also doch an einem Port. Das kann es nicht, wenn es sich nicht an ihn bindet, was nur erfolgreich ist, wenn die Kombination aus Port- und IP-Adresse frei ist....

                  korrekt geraten hier m:

                    
                              Socket m = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);  
                              m.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, true);  
                              EndPoint ipep = (EndPoint)(new IPEndPoint(IPAddress.Parse("{Lokale IP}"), 0));  
                              m.Bind(ipep);
                  

                  Beachte dass ich mittels SocketOption sage, dass ich den Header selbst mitgebe und dass der IPEndPoint zu EndPoint gecastet wird, welchese keine Eigenschaft "Port" mehr hat.

                  Anschließend benutze ich in eine while(true) Schleife die m.Receive() Methode um die Daten in ein ByteArray zu stecken, welches dann als IPPacket pk weiterverarbeitet wird.

                  Lauscht also nicht an einem Port sondern auf dem IP

                  1. Hi!

                    korrekt geraten hier m:

                    Socket m = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);

                    m.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, true);
                                EndPoint ipep = (EndPoint)(new IPEndPoint(IPAddress.Parse("{Lokale IP}"), 0));
                                m.Bind(ipep);

                    
                    > Lauscht also nicht an einem Port sondern auf dem IP  
                      
                    Du bindest dich also zunächst für das Senden an die lokale IP-Adresse. Port 0 heißt eigentlich, dass das System sich selbst einen freien Port sucht.  
                      
                    
                    > Beachte dass ich mittels SocketOption sage, dass ich den Header selbst mitgebe und dass der IPEndPoint zu EndPoint gecastet wird, welchese keine Eigenschaft "Port" mehr hat.  
                      
                    Den Cast kannst du auch weglassen, der ändert am Ergebnis nichts und beschneidet auch das Objekt nicht. Man kann über ipep halt nur auf die in EndPoint deklarierten Mitglieder zugreifen, aber da werden dann auch die vom Erbnehmer überschriebenen verwendet. Ich hab mir mal (mit dem ILSpy) angeschaut, was Bind() macht. Letztlich werden doch die Daten von Adresse und Port verwendet, um das eigentliche Bind auszuführen. Vom übergebenen EndPoint wird Serialize() aufgerufen. Da du eigentlich ein IPEndPoint-Objekt übergeben hast, wird auch dessen Serialize() verwendet, was Adresse und Port berücksichtigt. Die serialisierten Daten werden zum Binden verwendet. EndPoint.Serialze() feuert nur eine Exception. Und die SocketOptions interessieren beim Binding auch nicht.  
                      
                    Da du nun aber ein rohes Paket verschickst, bei dem du selbst den Port gesetzt hast, ist mir nun nicht ganz klar, wie das Socket-Objekt nun an die Empfangsdaten kommt, will sagen, wie es weiß, welche Daten für es bestimmt sind. Mir ist ebenfalls nicht klar, warum du Headermanipulation machst und nicht das Port-Handling über die vorgesehenen Funktionen abwickelst.  
                      
                      
                    Lo!
                    
                    1. Hi!

                      Moin.
                      So, nun habe ich das Ganze nochmal anders aufgezogen.

                      1. Anfrage auf Port 53 von der ClientApp entgegennehmen
                      2. IP-Paket umbauen und auf einem beliebigen Port dem richtigen DNS-Server zur Verfügung stellen
                      3. Die Antwort vom "echten" DNS-Server entgegen nehmen
                      4. Das IP-Paket umbauen
                      5. Das Paket der ClientApp auf ihrem Anfrage-Port zurück geben (oder auch nicht - je nach Wunsch)

                      Lo!

                      Markus**

  3. hi,

    Allerdings ist mir das mit dem port +10/-10 nicht geheuer und ich hab im Moment keine ahnung wie ich eine freie Portnummer ermitteln kann. könnte ja mal sein, dass auf port n+10 schon ein anderer Dienst rumkriecht.

    Naja, ich bin auch oft im Layer 3 rumgekrochen, erst heute wieder, und da sind mir so Sachen wie SYN/ACK aufgefallen, damit lässt sich schon feststellen, ob ein Port frei ist ;)

    Hotti

    1. Hi!

      Allerdings ist mir das mit dem port +10/-10 nicht geheuer und ich hab im Moment keine ahnung wie ich eine freie Portnummer ermitteln kann. könnte ja mal sein, dass auf port n+10 schon ein anderer Dienst rumkriecht.
      Naja, ich bin auch oft im Layer 3 rumgekrochen, erst heute wieder, und da sind mir so Sachen wie SYN/ACK aufgefallen, damit lässt sich schon feststellen, ob ein Port frei ist ;)

      Es geht um DNS. Das läuft üblicherweise per UDP. Da gibt es kein SYN/ACK. Außerdem geht es darum, festzustellen ob ein Port auf der eigenen Maschine frei ist. Selbst wenn er TCP für die DNS-Abfrage nimmt (was bei DNS auch möglich ist), ist es wohl kaum der richtige Weg, eine Anfrage zu stellen und dann bis zum Timeout warten zu müssen, und das gegebenenfalls mehrfach zu wiederholen, um einen freien Port zu finden.

      Für UDP ist ein Verbindungstest nicht durchführbar, weil keine Verbindung zustandekommt. Ob UDP-Pakete irgendwo landen oder keinen Empfänger finden ist vom Sender aus nicht feststellbar.

      Gemäß deinem Vorschlag einen Port zu finden würde auch zu einem TOCTOU-Problem führen, wenn zwischen dem Test und dem Binden an den Port ein anderer Prozess schneller gewesen ist. Da er aber um UDP-Pakete empfangen zu können, sowieso ein Binding an einen Port unternehmen muss, erfährt er dabei sofort, ob er frei ist oder nicht. Für TCP gibt es das Problem des Portfindens üblicherweise nicht, weil man da in der Regel einem TCP-Client nur Ziel-IP und -Port bekanntgibt und den Rest das System erledigt.

      Lo!