GetAddrInfo() löst IPv6-Localhost falsch auf



  • Hallo!
    Ich wundere mich etwas, wieso folgender Code nicht wie erwartet funktioniert:

    ADDRINFOT hints = { 0 }, *ai = 0;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = Protocol::socket_type;
    hints.ai_protocol = Protocol::protocol_family;
    if(!host) hints.ai_flags = AI_PASSIVE;
    ICantBelieveThatIuseGoto:	
    switch(::GetAddrInfo(const_cast<TCHAR*>(host), 
    		     const_cast<TCHAR*>(service),
    	       	     &hints, 
    		     &ai)) 
    {
    case WSAHOST_NOT_FOUND:
    case EAI_AGAIN:
    	if(tryings > 0) {
    		--tryings;
    		goto ICantBelieveThatIuseGoto;
    	}
    	else
    		throw net_error("number of address resolve tryings exceeded");
    	break;
    case NO_ERROR:
    	break;
    default:
    	throw net_error("address resolving error");
    }
    

    Der Code hat immer gut funktioniert. Sobald ich als Host aber den String "localhost" oder "[::1]" u. als Port z.B. 1234 angebe, wird für IPv6 das immer auf [::]:1234 aufgelöst, also die Adresse ist komplett null. Woran kann das liegen? Für die IPv4-Interfaces klappt's wunderbar.



  • Hat keiner eine Idee? 😞



  • bring doch mal ein beispiel ohne dein framework und ohne son goto gedöns 😉



  • Hab das genauere Übel gefunden:

    #include <iostream>
    #include <WinSock2.h>
    
    int main()
    try {
    	WSADATA data = { 0 };
    	if(WSAStartup(MAKEWORD(2, 2), &data) == SOCKET_ERROR)
    		throw "startup error";
    	int tryings = 3;
    	ADDRINFOT hints = { 0 }, *ai = 0;
    	hints.ai_family = AF_UNSPEC;
    	hints.ai_socktype = SOCK_STREAM;
    	hints.ai_protocol = IPPROTO_TCP;
    	ICantBelieveThatIuseGoto:   
    	switch(::GetAddrInfo("localhost",
    						 "80",
    						 &hints,
    						 &ai))
    	{
    	case WSAHOST_NOT_FOUND:
    	case EAI_AGAIN:
    		if(tryings > 0) {
    			--tryings;
    			goto ICantBelieveThatIuseGoto;
    		}
    		else
    			throw "number of address resolve tryings exceeded";
    		break;
    	case NO_ERROR:
    		break;
    	default:
    		throw "address resolving error";
    	}
    	char buf[INET6_ADDRSTRLEN * 2] = { 0 };
    	DWORD length = INET6_ADDRSTRLEN * 2;
    	SOCKADDR_STORAGE ss = { 0 };
    	std::memcpy(&ss, ai->ai_addr, sizeof sockaddr);
    	if(::WSAAddressToString(reinterpret_cast<SOCKADDR*>(&ss), sizeof(SOCKADDR_STORAGE), 0, buf, &length) == SOCKET_ERROR) {
    		::FreeAddrInfo(ai);
    		throw "address to string conversion error";
    	}
    	std::cout << "Adress: " << buf << std::endl;
    	std::system("pause");
    	::WSACleanup();
    	::FreeAddrInfo(ai); 
    }
    catch(const char* msg) {
    	std::cerr << msg << "\nErrno: " << GetLastError() << '\n';
    	std::system("pause");
    	::WSACleanup();
    }
    

    An der Stelle wo ich den Inhalt von ai->ai_addr in ss kopiere, gehen irgendwie Informationen verloren. Als Ausgabe bekomme ich dann [::]:80. Wenn ich hingegen die ai->ai_addr direkt in WSAAdressToString() konvertiere, kommt tatsächlich [::1]:80.
    Das verrückte: wenn ich beim memcpy die Größe des Zielspeichers (also sizeof(SOCKADDR_STORAGE) angebe, wird undefinierter Speicher kopiert (da ja ai->ai_addr nur sizeof(SOCKADDR) hat). Allerdings ist genau dieses Byte richtig gesetzt, wodurch ich dann [::1]:80 erhalte (weiter hinten ist in der Sockaddr-Storage aber dann natürlich Speicher-Müll) -> undefiniertes Verhalten. Zufall?



  • Du musst beim memcpy ai->ai_addrlen benutzen und nicht sizeof sockaddr.



  • Hoppla, das ist peinlich. 😞
    Genau das war das Problem, vielen Dank! 🙂

    PS: Ich empfinde das Goto hier übrigens recht elegant. Innerhalb einer Schleife kann ich "break" durch das "switch()" nicht benutzen. Warum also erst irgendein Schleifen-Konstrukt mit "if..else..else if" basteln, wenn's mit Goto ganz unkompliziert geht? 🙂



  • Du könntest das ganze ja in einer Funktion kapseln, dann kannste return benutzen. 😉

    Also ungefähr so:

    void GetAddrInfoWithRetry(..., ..., ..., ...)
    {
    	const int RETRIES = 3;
    
    	for(int i = 0; i < RETRIES; ++i)
    	{
    		int result = GetAddrInfo(..., ..., ..., ...);
    
    		switch(result)
    		{
    			case WSAHOST_NOT_FOUND:
    			case EAI_AGAIN:
    				break;
    			case 0:
    				return;
    			default:
    				throw "address resolving error";
    		}
    	}
    
    	throw "number of address resolve tryings exceeded";
    }
    


  • Naja, void kann die Funktion ja nicht als Rückgabetyp haben, da diese ja die Adresse zurückgeben muss. Das kann natürlich dann im Fehlerfall ein 0-Zeiger sein. Das dann aber erst abzufragen etc. ist wesentlich mehr Aufwand als ein simples goto. 🤡


Anmelden zum Antworten