OpenNetworkConnection(): URL mit mehreren IPs

Für allgemeine Fragen zur Programmierung mit PureBasic.
Benutzeravatar
Sicro
Beiträge: 963
Registriert: 11.08.2005 19:08
Kontaktdaten:

OpenNetworkConnection(): URL mit mehreren IPs

Beitrag von Sicro »

OpenNetworkConnection löst eine angegebene URL in die IP-Adresse auf und verbindet mit dieser.

Was passiert, wenn die URL aufgelöst mehrere IP-Adressen hat? Werden dann alle durchprobiert bis die Verbindung hergestellt werden konnte?
alexander@home-pc:~$ host http://www.google.com
http://www.google.com has address 173.194.44.48
http://www.google.com has address 173.194.44.50
http://www.google.com has address 173.194.44.51
http://www.google.com has address 173.194.44.49
http://www.google.com has address 173.194.44.52
http://www.google.com has IPv6 address 2a00:1450:4016:802::1013
Bild
Warum OpenSource eine Lizenz haben sollte :: PB-CodeArchiv-Rebirth :: Pleasant-Dark (Syntax-Farbschema) :: RegEx-Engine (kompiliert RegExes zu NFA/DFA)
Manjaro Xfce x64 (Hauptsystem) :: Windows 10 Home (VirtualBox) :: Neueste PureBasic-Version
Dark
Beiträge: 93
Registriert: 24.08.2007 20:36
Kontaktdaten:

Re: OpenNetworkConnection(): URL mit mehreren IPs

Beitrag von Dark »

Hi,

falls sich nichts geändert hat, löst PureBasic bei OpenNetworkConnection() die DNS/Host namen mit einem API Befehl auf, welcher nur eine IP Adresse zurück gibt. Diese IP wird dann beim Verbindungsaufbau benutzt. Welche IP genau verwendet wird, hängt nun also von der API und dem DNS Server ab. Es wird also nur zufällig eine dieser IPs verwendet und nicht die komplette Liste durchprobiert.

Dark
Benutzeravatar
Sicro
Beiträge: 963
Registriert: 11.08.2005 19:08
Kontaktdaten:

Re: OpenNetworkConnection(): URL mit mehreren IPs

Beitrag von Sicro »

Alles klar, danke. Dann gehen wir mal davon aus, dass die API-Funktion das schon richtig macht und immer eine verbindungsfähige IP liefert.
Bild
Warum OpenSource eine Lizenz haben sollte :: PB-CodeArchiv-Rebirth :: Pleasant-Dark (Syntax-Farbschema) :: RegEx-Engine (kompiliert RegExes zu NFA/DFA)
Manjaro Xfce x64 (Hauptsystem) :: Windows 10 Home (VirtualBox) :: Neueste PureBasic-Version
Dark
Beiträge: 93
Registriert: 24.08.2007 20:36
Kontaktdaten:

Re: OpenNetworkConnection(): URL mit mehreren IPs

Beitrag von Dark »

Sicro hat geschrieben:Alles klar, danke. Dann gehen wir mal davon aus, dass die API-Funktion das schon richtig macht und immer eine verbindungsfähige IP liefert.
Die API interessiert das recht wenig ob die IP erreichbar ist. Du musst bedenken das die Befehle zum auflösen eines DNS Namens und der Verbindungsherstellung unabhängig sind. Der Befehl zum Auflösen des Namens weiß also gar nicht welchen Dienst/Port du erreichen möchtest und kann diesen daher auch nicht testen. Wenn du wirklich alle IPs durchprobieren willst, musst du die Auflösung per Hand machen und dann jede IP einzeln an OpenNetworkConnection() übergeben. Das sollte zwar nicht zu schwer sein, jedoch könnte dein Programm dann für einige Sekunden hängen bleiben (da OpenNetworkConnection() blocking ist). Daher ist die Frage ob dies unbedingt notwendig ist, denn asynchrone Sockets (welche nicht blockieren würden) sind wesentlich aufwändiger zu Programmieren.

Dark
Benutzeravatar
Sicro
Beiträge: 963
Registriert: 11.08.2005 19:08
Kontaktdaten:

Re: OpenNetworkConnection(): URL mit mehreren IPs

Beitrag von Sicro »

Natürlich kann die API-Funktion nicht vorab prüfen, ob die IPs erreichbar sind. Für einen Verbindung ist ja
immer noch ein Port anzugeben, wie du richtig sagst. Mein letzter Satz war nicht ganz durchdacht und
ergänze diesen zu "... hoffentlich immer eine verbindungsfähige IP liefert". :coderselixir:

Ich habe mir die API-Funktionen mal herausgesucht. Da hätten wir die Funktion "gethostbyname" (IPv4) und
die neuere Funktion "getaddrinfo" (IPv4, IPv6). Beide liefern mehrere IPs, wenn vorhanden. Die Einschränkung
liegt also nicht an den API-Funktionen, aber vielleicht bei den anderen Betriebsystemen ja doch. Wie sicher
bist du, dass PureBasic nicht mehrere IPs durchprobiert? Hast du das mal getestet?

OpenNetworkConnection hat ja in der neuen Beta den neuen Parameter "TimeOut", also wenn gewünscht kein
Blocking mehr - zumindest beim Verbindungsaufbau :allright:

Die Frage ist mir einfach spontan gekommen und ich wollte das gerne geklärt haben. Es ist also keine Entwicklung
von mir am Laufen, die von diesem Thema abhängt.
Bild
Warum OpenSource eine Lizenz haben sollte :: PB-CodeArchiv-Rebirth :: Pleasant-Dark (Syntax-Farbschema) :: RegEx-Engine (kompiliert RegExes zu NFA/DFA)
Manjaro Xfce x64 (Hauptsystem) :: Windows 10 Home (VirtualBox) :: Neueste PureBasic-Version
Dark
Beiträge: 93
Registriert: 24.08.2007 20:36
Kontaktdaten:

Re: OpenNetworkConnection(): URL mit mehreren IPs

Beitrag von Dark »

Du hast recht, ich hatte im Kopf gehabt, das die Funktion nur eine IP zurück liefert. In Wahrheit nutzt PureBasic einfach nur die erste zurück gelieferte IP. Ich habe mir mal die Mühe gemacht und die OpenNetworkConnection() für TCP (Disassembliert aus dem Code der neusten Beta) zerlegt und das ganze mal pseudomäßig als C Code geschrieben:

Code: Alles auswählen

SOCKET purebasic_OpneNetworkConnection_TCP(const char *host, unsigned short port, unsigned int timeout){

	if (socket(AF_INET, SOCK_STREAM, 0) != INVALID_SOCKET){

		unsigned long ip = inet_addr(host);

		if(ip == 0xFFFFFFFF){

			struct hostent* = gethostbyname(host);
			if(hostent != 0){
				ip = *(unsigned long*)hostent->h_addr_list[0];	
			}

		}

		if(ip != 0xFFFFFFFF){

			sockaddr_in addr;
			addr.sin_family 	= AF_INET;
			addr.sin_addr.s_addr 	= ip;
			addr.sin_port		= htons(port);

			if ( timeout > 0) {

				unsignd long mode;
				mode = 1;
				ioctlsocket(s, FIONBIO, &mode);		
			
				if ( connect(s, addr, sizeof(sockaddr_in)) == -1 ){

					if( ioctlsocket(s, FIONBIO, &mode) == 0 ){

						fd_set write;
						fd_set except;

						FD_SET(s, write);
						FD_SET(s, except);
					
						timeval time;
						time.tv_sec  = timeout / 1000;
						time.tv_usec = (timeout % 1000) * 1000;
										

						if ( select(0, 0, &write, &except, &time) != 0 ){
	
							if (FD_ISSET(s, &write)){
								return s;
							} 
						}
					}
				}
			}else{
				if ( connect(s, addr, sizeof(sockaddr_in)) == 0 ){
					return s;
				}
			}
		}
		closesocket(s);		
	}
	return INVALID_SOCKET;
}
Ich habe jetzt nicht jede Kleinigkeit nachgeschaut (wie z.B. PB genau den Timeout berechnet), aber im groben und ganzen stellt dies die Vorgehensweise von OpenNetworkConnection für TCP dar. Wie man sieht wird nur die erste IP benutzt und im Fehlerfall zurück gekehrt.
Benutzeravatar
Sicro
Beiträge: 963
Registriert: 11.08.2005 19:08
Kontaktdaten:

Re: OpenNetworkConnection(): URL mit mehreren IPs

Beitrag von Sicro »

Vielen Dank für deine aufgewendete Zeit und Mühe. Die direkte Vorgehensweise über die
WinAPI ist mir bekannt - so wird das wohl ablaufen. Anstelle von gethostbyname() wird
aber sicherlich seit der neuen Beta von PureBasic mit IPv6-Support getaddrinfo() verwendet.
Bild
Warum OpenSource eine Lizenz haben sollte :: PB-CodeArchiv-Rebirth :: Pleasant-Dark (Syntax-Farbschema) :: RegEx-Engine (kompiliert RegExes zu NFA/DFA)
Manjaro Xfce x64 (Hauptsystem) :: Windows 10 Home (VirtualBox) :: Neueste PureBasic-Version
Dark
Beiträge: 93
Registriert: 24.08.2007 20:36
Kontaktdaten:

Re: OpenNetworkConnection(): URL mit mehreren IPs

Beitrag von Dark »

Sicro hat geschrieben:Vielen Dank für deine aufgewendete Zeit und Mühe. Die direkte Vorgehensweise über die
WinAPI ist mir bekannt - so wird das wohl ablaufen. Anstelle von gethostbyname() wird
aber sicherlich seit der neuen Beta von PureBasic mit IPv6-Support getaddrinfo() verwendet.
Ich habe den Flag #PB_Network_IPv6 nicht übergeben und in dem Fall wird immer noch gethostbyname() verwendet. Mein Pseudocode ist aus dem disassemblierten Netzwerkcode von der Version 5.10 Beta 1 für Windows x86 (da OllyDbg kein 64 bit kann) entstanden und nicht anhand der normalen Vorgehensweise. Und dies sind 1:1 die API Befehle (mit den ausgelesen Parametern) die PureBasic verwendet. Hier ist auch noch mal die Import Tabelle von der erstellten Executable:

Code: Alles auswählen

        DLL Name: WSOCK32.DLL
        vma:  Hint/Ord Member-Name Bound-To
        41f0        0  closesocket
        41fe        0  WSACleanup
        420c        0  WSAStartup
        421a        0  connect
        4224        0  socket
        422e        0  inet_addr
        423a        0  gethostbyname
        424a        0  htons
        4252        0  bind
        425a        0  ioctlsocket
        4268        0  select
        4272        0  __WSAFDIsSet
Wie du siehst kommt getaddrinfo() gar nicht vor, jedoch gethostbyname() (Für IPv6 wird getaddrinfo() wahrscheinlich per LoadLibrary() und GetProcAddress() nachgeladen, da es erst ab Windows 2000 Prof. verfügbar ist). Würde gethostbyname() im IPv4 Fall nicht mehr verwendet, wäre der Import irgendwie sinnlos. Du kannst dies ja auch gerne selbst überprüfen, in dem du den Debugger deiner Wahl nimmst, einen Breakpoint auf gethostbyname() in WSOCK32 setzt und dann schaust ob der Befehl aufgerufen wird.

Ich habe mal schnell die IPv6 Variante unter Linux dissasembliert (da ich normalerweise Linux verwende und jetzt nicht wieder in Windows booten wollte). Folgender PB Code:

Code: Alles auswählen

EnableExplicit

InitNetwork()
Define.i Connection = OpenNetworkConnection("google.de", 80, #PB_Network_IPv6)

If Connection
    CloseNetworkConnection(Connection)
EndIf
und mal nachgeschaut welche Netzwerke Befehle im IPv6 Fall zum Einsatz kommen:

Code: Alles auswählen

  40214a:	e8 b1 e8 ff ff       	callq  400a00 <close@plt>
  402166:	e8 95 e8 ff ff       	callq  400a00 <close@plt>
  40222a:	e8 71 e8 ff ff       	callq  400aa0 <connect@plt>
  40223f:	e8 5c e8 ff ff       	callq  400aa0 <connect@plt>
  4022ef:	e8 cc e7 ff ff       	callq  400ac0 <socket@plt>
  40230a:	e8 11 e7 ff ff       	callq  400a20 <inet_addr@plt>
  4023b0:	e8 3b e6 ff ff       	callq  4009f0 <ioctl@plt>
  4023c3:	e8 38 e6 ff ff       	callq  400a00 <close@plt>
  4023f3:	e8 48 e6 ff ff       	callq  400a40 <inet_pton@plt>
  40240f:	e8 9c e6 ff ff       	callq  400ab0 <getaddrinfo@plt>
  402496:	e8 e5 e5 ff ff       	callq  400a80 <bind@plt>
  4024c5:	e8 26 e5 ff ff       	callq  4009f0 <ioctl@plt>
  402557:	e8 94 e4 ff ff       	callq  4009f0 <ioctl@plt>
  4025e4:	e8 67 e4 ff ff       	callq  400a50 <select@plt>
  402637:	e8 e4 e3 ff ff       	callq  400a20 <inet_addr@plt>
  402653:	e8 d8 e3 ff ff       	callq  400a30 <gethostbyname@plt>
und da sehen wir dann auch getaddrinfo(). Schauen wir doch mal schnell ob hier auch nur die erste IP verwendet wird.

Code: Alles auswählen

  40240f:	e8 9c e6 ff ff       	call   400ab0 <getaddrinfo@plt>
  402414:	85 c0                	test   eax,eax
  402416:	75 a8                	jne    4023c0 <PB_CloseNetworkServer+0x2f0>
  402418:	48 8b 84 24 70 01 00 	mov    rax,QWORD PTR [rsp+0x170]
  40241f:	00 
  402420:	48 8b 50 18          	mov    rdx,QWORD PTR [rax+0x18]
  402424:	48 8b 42 08          	mov    rax,QWORD PTR [rdx+0x8]
  402428:	48 89 84 24 40 01 00 	mov    QWORD PTR [rsp+0x140],rax
  40242f:	00 
  402430:	48 8b 42 10          	mov    rax,QWORD PTR [rdx+0x10]
  402434:	48 89 84 24 48 01 00 	mov    QWORD PTR [rsp+0x148],rax
  40243b:	00 
  40243c:	e9 da fe ff ff       	jmp    40231b <PB_CloseNetworkServer+0x24b>
Es handelt sich um ein 64 Bit System, also ist (in C) ein int 4 Byte groß und ein long 8 byte. Als erstes wird der Rückgabewert von getaddrinfo überprüft. Ist dieser ungleich 0 wird weiter ausgeführt. Dann müssen wir noch wissen das rsp+0x170 unser Rückgabeparameter ist (also die API schreibt an diese Stelle die Liste mit den Addressen, siehe http://linux.die.net/man/3/getaddrinfo. Den Block wo die Parameter vorbereitet werden habe ich jetzt nicht mitkopiert). Es wird also aus der Struktur addrinfo an der Stelle 0x18 gelesen. 0x18 ist Dezimal 24. Werfen wir mal einen Blick auf die Offsets dieser Struktur:

Code: Alles auswählen

struct addrinfo {
	int              ai_flags;		- 0
	int              ai_family;		- 4
	int              ai_socktype;	- 8
	int              ai_protocol;	- 16
	size_t           ai_addrlen;	- 20
	struct sockaddr *ai_addr;		- 24
	char            *ai_canonname;
	struct addrinfo *ai_next;
};
Kannst du auch gerne mit folgendem C++ Code überprüfen:

Code: Alles auswählen

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <iostream>
#include <stdio.h>
#include <stddef.h>

int main(){
	std::cout << offsetof(addrinfo, ai_addr) << std::endl;
}
Ergibt ebenfalls 24. So nun wird in dieser Struktur an dem Offset 0x8 (also 8 ) und 0x10 (also 16) ein Wert ausgelesen. Also werfen wir auch mal einen Blick auf die Struktur:

Code: Alles auswählen

struct sockaddr_in6 {
		short   sin6_family;			- 0 
		u_short sin6_port;				- 2
		u_long  sin6_flowinfo;			- 4
		struct  in6_addr sin6_addr{
			union {
				u_char  Byte[16];		- 8
				u_short Word[8];		- 8
		  	} u;			
		};
		u_long  sin6_scope_id;			- 24
};
Und wie wir sehen wird mit zwei MOVQ Befehlen die erste zurück gelieferte IPv6 Addresse nach rsp+0x140 kopiert. Da dies die einzige Stelle im Code ist wo getaddrinfo() verwendet wird und hier hard coded die erste Adresse ausgelesen wird, zeigt dies das auch nur die erste IP verwendet wird. Schaut man sich die Imports und des Programms an fällt auf das die Liste (welche von der API im Befehl getaddrinfo() allocated wird) niemals per freeaddrinfo() freigegeben wird :(. Mit Hilfe des Tools ltrace kann man sich alle Bibliotheksaufrufe anzeigen und erhält:

Code: Alles auswählen

__libc_start_main(0x402000, 1, 0x7fff59cb8b68, 0x402b50, 0x402be0 <unfinished ...>
malloc(16)                                                                             = 0x0101d010
malloc(64)                                                                             = 0x0101d030
memset(0x0101d030, '\000', 64)                                                         = 0x0101d030
malloc(136)                                                                            = 0x0101d080
memset(0x0101d080, '\000', 136)                                                        = 0x0101d080
socket(10, 1, 0)                                                                       = 3
inet_pton(10, 0x6040b5, 0x7fff59cb89d0, -1, 0)                                         = 0
getaddrinfo("google.de", NULL, NULL, 0x7fff59cb8a00)                                   = 0
connect(3, 0x7fff59cb89a0, 28, 0x7fff59cb89a0, 3)                                      = -1
close(3)                                                                               = 0
free(0x0101d010)                                                                       = <void>
exit(0 <unfinished ...>
Hier fehlt auch jede Spur von freeaddrinfo(). Es scheint wohl ein Memoryleak in der Funktion zu existieren. Wenn du eine DNS findest, welche mehrere IPv6 Adressen zurück liefert, kann ich dir mit dem Programm auch zeigen das jeweils nur ein connect() aufgerufen wird. Hier schlägt das connecten zwar auch fehl (Rückgabewert -1), jedoch ist in dem DNS Eintrag von Google nur eine IPv6 Adresse angegeben.

Dark
Benutzeravatar
Sicro
Beiträge: 963
Registriert: 11.08.2005 19:08
Kontaktdaten:

Re: OpenNetworkConnection(): URL mit mehreren IPs

Beitrag von Sicro »

Dark hat geschrieben:Mein Pseudocode ist aus dem disassemblierten Netzwerkcode von der Version 5.10 Beta 1
für Windows x86 (da OllyDbg kein 64 bit kann) entstanden und nicht anhand der normalen
Vorgehensweise. Und dies sind 1:1 die API Befehle (mit den ausgelesen Parametern) die
PureBasic verwendet.
Ok, wusste nicht, wie genau die Informationen sind, die man durch das Disassemblieren
bekommt. Kenne mich in diesem Gebiet noch nicht so gut aus.
Dark hat geschrieben:Wie du siehst kommt getaddrinfo() gar nicht vor, jedoch gethostbyname() (Für IPv6 wird
getaddrinfo() wahrscheinlich per LoadLibrary() und GetProcAddress() nachgeladen, da es erst ab
Windows 2000 Prof. verfügbar ist). [...] Du kannst dies ja auch gerne selbst überprüfen [...]
So ist es natürlich besser gelöst. Die Kompatibilität zu älteren Betriebsystemen sollte ja noch bestehen
bleiben. Einen eigenen Test brauche ich nicht durchführen, ich glaube dir das auch so gerne.

Bei IPv6 sind dann halt gethostbyname() und getaddrinfo() geladen, wie bei dir weiter unten
zu sehen ist, aber wenn juckt das schon.
Dark hat geschrieben:Schaut man sich die Imports und des Programms an fällt auf das die Liste (welche von der API
im Befehl getaddrinfo() allocated wird) niemals per freeaddrinfo() freigegeben wird :(.
Ich habe dazu nochmal kurz die Changelog von der neuen PureBasic Beta überflogen. Dort ist der Befehl
FreeIP() im Zusammenhang mit IPv6 zu lesen, der für die Freigabe der Liste zuständig sein müsste.
Dark hat geschrieben:Wenn du eine DNS findest, welche mehrere IPv6 Adressen zurück liefert, kann ich dir mit
dem Programm auch zeigen das jeweils nur ein connect() aufgerufen wird.
Ich glaube dir das auch so, aber wenn du Zeit und Lust hast, könntest du das gerne mal ausprobieren.
Wer weiß, vielleicht strickt PureBasic dort ja plötzlich einen anderen Code.

Über die Wikipedia zum Thema IPv6 bin ich auf die Internetseite wwww.sixy.ch gestossen, wo
Internetseiten mit IPv6-Unterstützung gelistet werden. Manche von diesen haben auch mehrere
IPv6-Adressen, z.B. http://www.safecomprogram.gov


Ich danke dir auf jeden Fall nochmal für diese sehr ausführliche Erklärung und Nachforschung. :allright:
Bild
Warum OpenSource eine Lizenz haben sollte :: PB-CodeArchiv-Rebirth :: Pleasant-Dark (Syntax-Farbschema) :: RegEx-Engine (kompiliert RegExes zu NFA/DFA)
Manjaro Xfce x64 (Hauptsystem) :: Windows 10 Home (VirtualBox) :: Neueste PureBasic-Version
Dark
Beiträge: 93
Registriert: 24.08.2007 20:36
Kontaktdaten:

Re: OpenNetworkConnection(): URL mit mehreren IPs

Beitrag von Dark »

Sicro hat geschrieben:Ich habe dazu nochmal kurz die Changelog von der neuen PureBasic Beta überflogen. Dort ist der Befehl
FreeIP() im Zusammenhang mit IPv6 zu lesen, der für die Freigabe der Liste zuständig sein müsste.
Das wäre irgendwie sinnlos, da man in seinem Programm die IP ja nie erhält und die sofort freigegeben werden könnte. Außerdem verlangt FreeIP() als Parameter die freizugebende IP, welche uns ja nicht bekannt ist. Daher ist FreeIP() auf jeden Fall nicht für diesen Zusammenhang gedacht.

Ich habe das ganze mal mit http://www.safecomprogram.gov getestet. Kurze Überprüfung ob auch wirklich mehrere IPs zurück geliefert werden:

Code: Alles auswählen

$ host www.safecomprogram.gov
www.safecomprogram.gov is an alias for www.safecomprogram.gov.edgesuite.net.
www.safecomprogram.gov.edgesuite.net is an alias for a1903.dscb.akamai.net.
a1903.dscb.akamai.net has address 95.100.249.105
a1903.dscb.akamai.net has address 95.100.249.97
a1903.dscb.akamai.net has IPv6 address 2a02:26f0:5::5f64:f961
a1903.dscb.akamai.net has IPv6 address 2a02:26f0:5::5f64:f969
Okay, dann testen wir mal:

Code: Alles auswählen

__libc_start_main(0x402000, 1, 0x7fff19d8b728, 0x402b50, 0x402be0 <unfinished ...>
malloc(16)                                               = 0x010e3010
malloc(64)                                               = 0x010e3030
memset(0x010e3030, '\000', 64)                           = 0x010e3030
malloc(136)                                              = 0x010e3080
memset(0x010e3080, '\000', 136)                          = 0x010e3080
socket(10, 1, 0)                                         = 3
inet_pton(10, 0x6040b5, 0x7fff19d8b590, -1, 0)           = 0
getaddrinfo("www.safecomprogram.gov", NULL, NULL, 0x7fff19d8b5c0) = 0
connect(3, 0x7fff19d8b560, 28, 0x7fff19d8b560, 3)        = -1
close(3)                                                 = 0
free(0x010e3010)                                         = <void>
exit(0 <unfinished ...>
Wieder nur einmal connect, obwohl dieser fehlschlägt. Es wird also wirklich nur eine IP getestet und der Speicher wird nicht korrekt freigegeben (zu mindestens unter Linux).

Dark
Antworten