From db3a7e348372b656243879baa92072d5f0c28ef7 Mon Sep 17 00:00:00 2001 From: roddy <45442056+RoddyDev@users.noreply.github.com> Date: Sun, 1 Sep 2024 13:48:24 -0300 Subject: [PATCH] Update sockets and structs for full IPv6 support (#198) * Initial IPv6 support The address struct was changed to use sockaddr_in6 instead of sockaddr_in. Resolving domains also uses getaddrinfo() instead of the deprecated gethostbyname(), and if the returned address is IPv4, transform it to an IPv6-mapped address to make it work with the AF_INET6 socket. * Direct Connection input box behaviour change This makes the Direct Connection input box accept all types of addresses. IPv4, IPv6 and hostnames are now supported. Direct IPv6 addresses must be enclosed in square brackets [IPv6]:port to be able to separate IP from port, otherwise it will be treated as an IPv4 address or hostname. * sanity checks This fixes a weird error while reconnecting when using direct IPv6 addresses. getaddrinfo() doesn't like square brackets on the host IP, so remove those when found. This commit also fixes a problem where gGetHostName wasn't being properly set when connecting through a hostname. * cleanup and bugfix Small code cleanup removing some debugging comments. This also fixes a bug where initializing the game without the --join argument (which initializes network as NT_NONE) was still calling a piece of code where it was resolving a domain and copying an empty gGetHostName to configJoinIp, which made coop_join_ip be cleared on the config file. * fix rare cases of binding errors Due to some options in the sockaddr_in6 struct that doesn't need to be set, rare cases can happen that there will be some random data that will interfere with such values. This made binding randomly or persistently fail. This commit makes that before binding, make sure to fill the entire struct with 0 before setting it up. * Translations for WARN_SOCKET This provides translations for the new text when hosting a server. I cannot guarantee the accuracy of the translations, except English, Portuguese and German, where it was written by native speakers. * Update socket_windows.c yeah right. resolving conflicts and stuff. --- lang/Czech.ini | 2 +- lang/Dutch.ini | 2 +- lang/English.ini | 2 +- lang/French.ini | 2 +- lang/German.ini | 2 +- lang/Italian.ini | 2 +- lang/Polish.ini | 2 +- lang/Portuguese.ini | 2 +- lang/Russian.ini | 2 +- lang/Spanish.ini | 2 +- src/pc/djui/djui_panel_host_message.c | 6 +- src/pc/djui/djui_panel_join_direct.c | 63 +++++++++-- src/pc/network/socket/socket.c | 146 ++++++++++++++++++------- src/pc/network/socket/socket_linux.c | 11 +- src/pc/network/socket/socket_windows.c | 9 +- 15 files changed, 192 insertions(+), 63 deletions(-) diff --git a/lang/Czech.ini b/lang/Czech.ini index cc5281ca..ac3cd95f 100644 --- a/lang/Czech.ini +++ b/lang/Czech.ini @@ -167,7 +167,7 @@ LOCAL_PLAYER_MODEL_ONLY = "Pouze lokální model hráče" INFO_TITLE = "INFO" WARN_DISCORD = "Pozvat hráče pravým kliknutím na jejich profil a potom kliknout na \n'\\#d0d0ff\\Pozvat do Hry\\#dcdcdc\\'.\n\nMůžete pozvat i kanály a servery pomocí kliknutí na tlačíto \\#d0d0ff\\plus\\#dcdcdc\\ vedle okna na chat.\n\nHerní aktivita \\#ffa0a0\\musí být\\#dcdcdc\\ zapnutá ve vašich\nDiscord nastavení.\n\nZobranení jako neviditelný \\#ffa0a0\\zabrání\\#dcdcdc\\ posílání pozvánek." WARN_DISCORD2 = "\\#ffa0a0\\Chyba:\\#dcdcdc\\ Discord se nepodařilo najít.\n\\#a0a0a0\\Zkuste zavřít hru, restartovat Discord a znovu hru otevřít" -WARN_SOCKET = "Přímé spojení \\#ffa0a0\\vás vyžaduje,\\#dcdcdc\\ aby jste si nastavili přesměrování portu.\n\nPřesměrujte port '\\#d0d0ff\\%d\\#dcdcdc\\' s UDP." +WARN_SOCKET = "Ujistěte se, že je vaše brána firewall správně nakonfigurována.\nPřímá připojení \\#ffa0a0\\vyžadují\\#dcdcdc\\, abyste v routeru nakonfigurovali přesměrování portů pro přijetí příchozích IPv4 připojení.\n\nPřesměrujte port '\\#d0d0ff\\%d\\#dcdcdc\\' pro UDP. IPv6 je také podporováno." HOST = "Hostovat" [HOST_MODS] diff --git a/lang/Dutch.ini b/lang/Dutch.ini index ec15f3a8..48b09b8e 100644 --- a/lang/Dutch.ini +++ b/lang/Dutch.ini @@ -167,7 +167,7 @@ LOCAL_PLAYER_MODEL_ONLY = "Alleen lokaal spelermodel" INFO_TITLE = "INFORMATIE" WARN_DISCORD = "Nodig je vrienden uit door op hun reachts klik op hun username te gebruiken en op '\\#d0d0ff\\Invite to Game\\#dcdcdc\\' te klikken.\n\nJe kan kanalen van servers ook uitnodigen door op de \\#d0d0ff\\plus\\#dcdcdc\\ knop te drukken naast de plek waar je chat.\n\nGame activiteit \\#ffa0a0\\moet\\#dcdcdc\\ aaan staan in je \nDiscord gebruikers opties.\n\nOp offline staan \\#ffa0a0\\houd uitnodigingen versturen tegen.\\#dcdcdc\\ " WARN_DISCORD2 = "\\#ffa0a0\\Error:\\#dcdcdc\\ Kan Discord niet vinden.\n\\#a0a0a0\\Probeer om het spel af te sluiten, Discord opnieuw opstarten, en het spel weer op starten." -WARN_SOCKET = "Directe verbindingen \\#ffa0a0\\verplichten je\\#dcdcdc\\ om je router te port-forwarden.\n\nForward port '\\#d0d0ff\\%d\\#dcdcdc\\' for UDP." +WARN_SOCKET = "Zorg ervoor dat uw firewall correct is geconfigureerd.\nDirecte verbindingen \\#ffa0a0\\vereisen\\#dcdcdc\\ dat u poortdoorschakeling configureert in uw router om IPv4 inkomende verbindingen te accepteren.\n\nSchakel poort '\\#d0d0ff\\%d\\#dcdcdc\\' door voor UDP. IPv6 wordt ook ondersteund." HOST = "Organisator" [HOST_MODS] diff --git a/lang/English.ini b/lang/English.ini index 5cbb9df6..01b6bc87 100644 --- a/lang/English.ini +++ b/lang/English.ini @@ -167,7 +167,7 @@ LOCAL_PLAYER_MODEL_ONLY = "Local Player Model Only" INFO_TITLE = "INFO" WARN_DISCORD = "Invite friends by right clicking their name on Discord and clicking on\n'\\#d0d0ff\\Invite to Game\\#dcdcdc\\'.\n\nYou can invite channels of servers as well by clicking the \\#d0d0ff\\plus\\#dcdcdc\\ button next to the place where you enter chat.\n\nGame Activity \\#ffa0a0\\must be\\#dcdcdc\\ enabled in your\nDiscord user settings.\n\nAppearing offline \\#ffa0a0\\will prevent\\#dcdcdc\\ invites from being sent." WARN_DISCORD2 = "\\#ffa0a0\\Error:\\#dcdcdc\\ Could not detect Discord.\n\n\\#a0a0a0\\Try closing the game,\nrestarting Discord,\nand opening the game again." -WARN_SOCKET = "Direct connections \\#ffa0a0\\require you\\#dcdcdc\\ to configure port forwarding in your router.\n\nForward port '\\#d0d0ff\\%d\\#dcdcdc\\' for UDP." +WARN_SOCKET = "Make sure your firewall is properly configured.\nDirect connections \\#ffa0a0\\requires you\\#dcdcdc\\ to configure port forwarding in your router to accept IPv4 inbound connections.\n\nForward port '\\#d0d0ff\\%d\\#dcdcdc\\' for UDP. IPv6 is also supported." HOST = "Host" [HOST_MODS] diff --git a/lang/French.ini b/lang/French.ini index 55a1fc71..e29c207a 100644 --- a/lang/French.ini +++ b/lang/French.ini @@ -167,7 +167,7 @@ LOCAL_PLAYER_MODEL_ONLY = "Modèle de joueur local seulement" INFO_TITLE = "INFORMATIONS" WARN_DISCORD = "Invitez des amis en faisant un clic droit sur \nleur pseudo Discord puis en cliquant sur \n'\\#d0d0ff\\Inviter à rejoindre\\#dcdcdc\\'.\n\nVous pouvez envoyer des invitations dans les chats de serveurs en cliquant\nsur le bouton \\#d0d0ff\\+\\#dcdcdc\\ à coté de la barre de chat.\n\nLe statut d'activité \\#ffa0a0\\doit-être\\#dcdcdc\\ activé dans les paramètres utilisateurs Discord.\n\nApparaître hors-ligne \\#ffa0a0\\empêchera\\#dcdcdc\\ les invitations\nd'être envoyées." WARN_DISCORD2 = "\\#ffa0a0\\Erreur:\\#dcdcdc\\ Discord n'est pas détecté.\n\n\\#a0a0a0\\Essayez de fermer le jeu,\nrelancer Discord,\net relancer le jeu." -WARN_SOCKET = "La Connexion Directe \\#ffa0a0\\vous oblige\\#dcdcdc\\ à configurer un\nport dans votre routeur.\n\nDéfinissez le port sur '\\#d0d0ff\\%d\\#dcdcdc\\' pour l'UDP." +WARN_SOCKET = "Assurez-vous que votre pare-feu est correctement configuré.\nLes connexions directes \\#ffa0a0\\vous oblige\\#dcdcdc\\ à configurer le transfert de port sur votre routeur pour accepter les connexions entrantes IPv4.\n\nRedirigez le port '\\#d0d0ff\\%d\\#dcdcdc\\' pour UDP. IPv6 est également pris en charge." HOST = "Héberger" [HOST_MODS] diff --git a/lang/German.ini b/lang/German.ini index 52af0e1c..01152f04 100644 --- a/lang/German.ini +++ b/lang/German.ini @@ -167,7 +167,7 @@ LOCAL_PLAYER_MODEL_ONLY = "Nur lokales Spielermodell" INFO_TITLE = "INFO" WARN_DISCORD = "Lade Freunde über Discord ein, indem du rechtsklick auf ihren Namen machst und '\\#d0d0ff\\Zum Spiel einladen\\#dcdcdc\\' auswählst. Kanäle können auch über das \\#d0d0ff\\Plus-Symbol\\#dcdcdc\\ eingeladen werden. Stelle sicher, dass die Spielaktivität in den Discord-Einstellungen aktiviert ist. Wenn du offline angezeigt wirst, kannst du keine Einladungen senden." WARN_DISCORD2 = "\\#ffa0a0\\Fehlermeldung:\\#dcdcdc\\ Discord nicht gefunden. Versuche das Spiel zu schließen, Discord zu starten und dann das Spiel erneut zu öffnen." -WARN_SOCKET = "Für direkte Verbindungen \\#ffa0a0\\musst du\\#dcdcdc\\ die Portweiterleitung in deinem Router konfigurieren. Leite den Port '\\#d0d0ff\\%d\\#dcdcdc\\' für UDP weiter." +WARN_SOCKET = "Stelle sicher, dass Deine Firewall ordnungsgemäß konfiguriert ist. Direkte Verbindungen \\#ffa0a0\\erfordern\\#dcdcdc\\ die Konfiguration der Portweiterleitung (Port Forwarding) in Deinem Router, um eingehende IPv4-Verbindungen zu akzeptieren.\n\nLeite den Port '\\#d0d0ff\\%d\\#dcdcdc\\' für UDP weiter. IPv6 wird ebenfalls unterstützt." HOST = "Hosten" [HOST_MODS] diff --git a/lang/Italian.ini b/lang/Italian.ini index 0894572d..84995d7f 100644 --- a/lang/Italian.ini +++ b/lang/Italian.ini @@ -165,7 +165,7 @@ LOCAL_PLAYER_MODEL_ONLY = "Solo modello giocatore locale" INFO_TITLE = "INFO" WARN_DISCORD = "Invita gli amici facendo tasto destro sul loro nome in Discord e cliccando\n'\\#d0d0ff\\Invito a giocare\\#dcdcdc\\'.\n\npuoi invitare anche i canali dei server cliccando il pulsante \\#d0d0ff\\più\\#dcdcdc\\ vicino al posto dove scrivi.\n\nLo Stato delle Attività \\#ffa0a0\\deve essere\\#dcdcdc\\ attivo nelle\nimpostazioni utente di Discord.\n\nApparire offline \\#ffa0a0\\ti impedirà\\#dcdcdc\\ di inviare inviti." WARN_DISCORD2 = "\\#ffa0a0\\Errore:\\#dcdcdc\\ Impossibile individuare Discord.\n\n\\#a0a0a0\\prova a chiudre il gioco,\nriavviare Discord,\ne aprire di nuovo il gioco." -WARN_SOCKET = "La connessione diretta \\#ffa0a0\\richiede\\#dcdcdc\\ una configurazione port forwarding nel tuo router.\n\nTrasmetti una connessione '\\#d0d0ff\\%d\\#dcdcdc\\' per l'UDP." +WARN_SOCKET = "Assicurati che il tuo firewall sia configurato correttamente.\nLe connessioni dirette \\#ffa0a0\\richiede\\#dcdcdc\\ configurare l'inoltro delle porte nel tuo router per accettare connessioni in entrata IPv4.\n\nInoltra la porta '\\#d0d0ff\\%d\\#dcdcdc\\' per UDP. IPv6 è anche supportato." HOST = "Crea" [HOST_MODS] diff --git a/lang/Polish.ini b/lang/Polish.ini index c038876f..3c8b999b 100644 --- a/lang/Polish.ini +++ b/lang/Polish.ini @@ -167,7 +167,7 @@ LOCAL_PLAYER_MODEL_ONLY = "Tylko lokalny model gracza" INFO_TITLE = "INFORMACJA" WARN_DISCORD = "Zaproś znajomych do gry, klikając PPM w ich nazwę na Discordzie, a potem klikając\n'\\#d0d0ff\\Zaproś do gry\\#c8c8c8\\'.\n\nMożesz zapraszać na kanałach serwerów klikając w \\#d0d0ff\\plusik\\#c8c8c8\\ obok paska czatu.\n\n \\#ffa0a0\\Należy\\#c8c8c8\\ mieć włączoną Aktywność w grze w\nUstawieniach użytkownika Discorda.\n\nTryb offline \\#ffa0a0\\uniemożliwi\\#c8c8c8\\ wysyłanie zaproszeń." WARN_DISCORD2 = "\\#ffa0a0\\Błąd:\\#c8c8c8\\ Nie wykryto Discorda.\n\n\\#a0a0a0\\Spróbuj zamknąć grę,\nzrestartować Discorda\ni uruchomić grę ponownie." -WARN_SOCKET = "Połączenie bezpośrednie \\#ffa0a0\\wymaga\\#c8c8c8\\ konfiguracji przekierowania portów w routerze.\n\nPrzekieruj port '\\#d0d0ff\\%d\\#c8c8c8\\' dla UDP." +WARN_SOCKET = "Upewnij się, że twoja zapora jest poprawnie skonfigurowana.\nBezpośrednie połączenia \\#ffa0a0\\wymagają\\#dcdcdc\\ skonfigurowania przekierowania portów w routerze, aby akceptować przychodzące połączenia IPv4.\n\nPrzekieruj port '\\#d0d0ff\\%d\\#dcdcdc\\' dla UDP. IPv6 jest również obsługiwane." HOST = "Hostuj" [HOST_MODS] diff --git a/lang/Portuguese.ini b/lang/Portuguese.ini index 1b3c57cf..e756ea23 100644 --- a/lang/Portuguese.ini +++ b/lang/Portuguese.ini @@ -167,7 +167,7 @@ LOCAL_PLAYER_MODEL_ONLY = "Apenas modelo de jogador local" INFO_TITLE = "INFO" WARN_DISCORD = "Convide amigos clicando com o botão direito do mouse no nome deles no Discord e clicando em\n'\\#d0d0ff\\convidar para o jogo\\#dcdcdc\\'.\n\n Você também pode convidar em canais de servidores clicando no símbolo de \\#d0d0ff\\mais\\#dcdcdc\\ na caixa de texto abaixo das mensagens.\n\nÉ preciso configurar a \\#ffa0a0\\privacidade das atividades\\#dcdcdc\\ nas suas\n configurações do Discord.\n\n Se o seu status estiver como offline, \\#ffa0a0\\não poderá\\#dcdcdc\\ enviar convites." WARN_DISCORD2 = "\\#ffa0a0\\Erro:\\#dcdcdc\\ Discord não detectado.\n\n\\#a0a0a0\\tente fechar o jogo,\nreiniciar o Discord,\n e abrir o jogo novamente." -WARN_SOCKET = "Você precisa \\#ffa0a0\\configurar o encaminhamento de porta em seu roteador\\#dcdcdc\\ para usar a conexão direta.\n\nAbre porta '\\#d0d0ff\\%d\\#dcdcdc\\' no protocolo UDP." +WARN_SOCKET = "Verifique se o seu firewall está bem configurado.\nAo usar conexão direta, você precisa \\#ffa0a0\\configurar o encaminhamento de porta em seu roteador\\#dcdcdc\\ para aceitar conexões entrantes em IPv4.\n\nEncaminhe a porta '\\#d0d0ff\\%d\\#dcdcdc\\' para UDP. IPv6 também é suportado." HOST = "Criar" [HOST_MODS] diff --git a/lang/Russian.ini b/lang/Russian.ini index 5fbf4ced..f03f4759 100644 --- a/lang/Russian.ini +++ b/lang/Russian.ini @@ -166,7 +166,7 @@ LOCAL_PLAYER_MODEL_ONLY = "Только локальная модель игро INFO_TITLE = "INFO" WARN_DISCORD = "Пригласите друзей, щелкнув правой кнопкой мыши их имя в Дискорд, и, выбрав\n'\\#d0d0ff\\Пригласить в игру\\#dcdcdc\\'.\n\nВы также можете пригласить каналы серверов, нажав кнопку \\#d0d0ff\\плюс,\\#dcdcdc\\ кнопку рядом с местом входа в чат.\n\nИгровая активность \\#ffa0a0\\должна быть\\#dcdcdc\\ включена в ваших\n настройках Дискорда.\n\nИспользование офлайн статуса \\#ffa0a0\\предотвратит отправку \\#dcdcdc\\ приглашений." WARN_DISCORD2 = "\\#ffa0a0\\Error:\\#dcdcdc\\ Не удалось обнаружить Дискорд.\n\n\\#a0a0a0\\Попробуйте закрыть игру,\nперезапустите Дискорд,\nи снова откройте игру." -WARN_SOCKET = "Локальные соединения\\#ffa0a0\\требуют\\#dcdcdc\\ настроить переадресацию портов в роутере.\n\nПереадресация портов '\\#d0d0ff\\%d\\#dcdcdc\\' для UDP." +WARN_SOCKET = "Убедитесь, что ваш файрвол настроен правильно.\nПрямые подключения \\#ffa0a0\\требуют от вас\\#dcdcdc\\ настройки проброса портов на вашем маршрутизаторе для приeма входящих подключений по IPv4.\n\nПеренаправьте порт '\\#d0d0ff\\%d\\#dcdcdc\\' для UDP. IPv6 также поддерживается." HOST = "Хост" [HOST_MODS] diff --git a/lang/Spanish.ini b/lang/Spanish.ini index de970f56..90f9fc7d 100644 --- a/lang/Spanish.ini +++ b/lang/Spanish.ini @@ -167,7 +167,7 @@ LOCAL_PLAYER_MODEL_ONLY = "Solo modelo de jugador local" INFO_TITLE = "INFO" WARN_DISCORD = "Invita a amigos haciendo click derecho en su nombre en Discord y seleccionando\n'\\#d0d0ff\\Invitar a unirse\\#dcdcdc\\'.\n\nPuedes invitar en canales de un servidor también presionando el botón \\#d0d0ff\\+\\#dcdcdc\\ al lado del cuadro de texto del chat.\n\nEl estado de Actividad Actual \\#ffa0a0\\debe estar\\#dcdcdc\\ activado en tus ajustes de Discord.\n\nEstar invisible \\#ffa0a0\\te prevendrá\\#dcdcdc\\ de crear invitaciones." WARN_DISCORD2 = "\\#ffa0a0\\Error:\\#dcdcdc\\ No se ha detectado Discord.\n\n\\#a0a0a0\\Prueba a cerrar el juego,\nreiniciar Discord,\ny abrir el juego de nuevo." -WARN_SOCKET = "Las conexiones directas \\#ffa0a0\\requieren\\#dcdcdc\\ que abras los puertos en tu router.\n\nAbre el puerto '\\#d0d0ff\\%d\\#dcdcdc\\' con protocolo UDP." +WARN_SOCKET = "Asegúrate de que tu firewall esté configurado correctamente.\nLas conexiones directas \\#ffa0a0\\requieren que\\#dcdcdc\\ configures el reenvío de puertos en tu router para aceptar conexiones entrantes IPv4.\n\nReenvía el puerto '\\#d0d0ff\\%d\\#dcdcdc\\' para UDP. IPv6 también es suportado." HOST = "Crear" [HOST_MODS] diff --git a/src/pc/djui/djui_panel_host_message.c b/src/pc/djui/djui_panel_host_message.c index 283b5a80..af08f5fc 100644 --- a/src/pc/djui/djui_panel_host_message.c +++ b/src/pc/djui/djui_panel_host_message.c @@ -51,9 +51,9 @@ void djui_panel_host_message_create(struct DjuiBase* caller) { char* warningMessage = NULL; bool hideHostButton = false; - warningLines = 5; - warningMessage = calloc(256, sizeof(char)); - snprintf(warningMessage, 256, DLANG(HOST_MESSAGE, WARN_SOCKET), configHostPort); + warningLines = 7; + warningMessage = calloc(512, sizeof(char)); + snprintf(warningMessage, 512, DLANG(HOST_MESSAGE, WARN_SOCKET), configHostPort); f32 textHeight = 32 * 0.8125f * warningLines + 8; diff --git a/src/pc/djui/djui_panel_join_direct.c b/src/pc/djui/djui_panel_join_direct.c index a2690505..433609e8 100644 --- a/src/pc/djui/djui_panel_join_direct.c +++ b/src/pc/djui/djui_panel_join_direct.c @@ -98,25 +98,70 @@ static void djui_panel_join_direct_ip_text_change(struct DjuiBase* caller) { static void djui_panel_join_direct_ip_text_set_new(void) { char buffer[256] = { 0 }; + char orig_ip[256] = { 0 }; if (snprintf(buffer, 256, "%s", sInputboxIp->buffer) < 0) { LOG_INFO("truncating IP"); } + // copy original buffer for storing to gGetHostName + memcpy(&orig_ip, &buffer, 256); + bool afterSpacer = false; + bool is_ipv6 = false; int port = 0; + + // check if address starts with [ (meaning it's a direct IPv6 address. + // This is needed because we need to know when to get the port number. Example: [2001:db8::1000]:7777 + // If this character is not in the first character in the buffer, it will be treated as an IPv4 address or hostname. + if (buffer[0] == '[') { + memcpy(&buffer, &buffer[1], 255); + is_ipv6 = true; + } + + if (is_ipv6) { + LOG_INFO("Detected direct IPv6 address"); + } else { + LOG_INFO("Detected direct IPv4 address or hostname"); + } + + // this needs cleaning for (int i = 0; i < 256; i++) { - if (buffer[i] == ' ' || buffer[i] == ':') { - buffer[i] = '\0'; - afterSpacer = true; - } else if (buffer[i] == '\0') { - break; - } else if (afterSpacer && buffer[i] >= '0' && buffer[i] <= '9') { - port *= 10; - port += buffer[i] - '0'; + // Direct IPv6 address + if (is_ipv6 == true) { + // Check if it reached end of address "]:", or a space as a fail safe. + if ((buffer[i] == ']') || buffer[i] == ' ') { + afterSpacer = true; + memset(&orig_ip, 0, 256); + memcpy(&orig_ip[1], &buffer, i+1); + buffer[i] = '\0'; + orig_ip[0] = '['; + // skip over the port separator + if (buffer[i+1] == ':') { + i += 1; + } + } else if (buffer[i] == '\0') { + break; + } else if (afterSpacer && buffer[i] >= '0' && buffer[i] <= '9') { + port *= 10; + port += buffer[i] - '0'; + } + } else { + // Direct IPv4 address or hostname + // Check if it reached end of address ":", or a space as a fail safe. + if (buffer[i] == ' ' || buffer[i] == ':') { + afterSpacer = true; + buffer[i] = '\0'; + memcpy(&orig_ip, &buffer, i+1); + } else if (buffer[i] == '\0') { + break; + } else if (afterSpacer && buffer[i] >= '0' && buffer[i] <= '9') { + port *= 10; + port += buffer[i] - '0'; + } } } - snprintf(gGetHostName, MAX_CONFIG_STRING, "%s", buffer); + snprintf(gGetHostName, MAX_CONFIG_STRING, "%s", orig_ip); if (snprintf(configJoinIp, MAX_CONFIG_STRING, "%s", buffer) < 0) { LOG_INFO("truncating IP"); } diff --git a/src/pc/network/socket/socket.c b/src/pc/network/socket/socket.c index c32447ad..edf17920 100644 --- a/src/pc/network/socket/socket.c +++ b/src/pc/network/socket/socket.c @@ -5,28 +5,91 @@ #include "pc/djui/djui.h" static SOCKET sCurSocket = INVALID_SOCKET; -static struct sockaddr_in sAddr[MAX_PLAYERS] = { 0 }; +static struct sockaddr_in6 sAddr[MAX_PLAYERS] = { 0 }; +struct addrinfo hints; +struct addrinfo *result, *i; char gGetHostName[MAX_CONFIG_STRING] = ""; -void resolve_domain(void) { - struct hostent *remoteHost = gethostbyname(configJoinIp); - if (remoteHost && remoteHost->h_addrtype == AF_INET) { - struct in_addr addr; - for (int i = 0; remoteHost->h_addr_list[i] != 0; i++) { - memcpy(&addr, remoteHost->h_addr_list[i], sizeof(struct in_addr)); - snprintf(configJoinIp, MAX_CONFIG_STRING, "%s", inet_ntoa(addr)); +// Resolves a hostname to an IP address. Current limitation: It still only gets the first address it sees and returns. +// getaddrinfo() is smart enough to prioritize IPv4 if the user is not in an IPv6 enabled network, so this shouldn't be a problem for now. +// TODO: Store all found addresses somewhere and make the game try to connect to each of them if one fails. +void resolve_domain(struct sockaddr_in6 *addr) { + // non zero value if getaddrinfo throws an error. + int error; + + // set hints + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; + + // sanity check: remove square brackets from configJoinIp. getaddrinfo doesn't like those, at least on Linux. + if (configJoinIp[0] == '[') { + LOG_INFO("sanity check: found opening square bracket on configJoinIp, removing it."); + for (int i = 0; i < MAX_CONFIG_STRING; i++) { + if (configJoinIp[i] == '\0') { break; } + if (configJoinIp[i] == ']') { + configJoinIp[i] = '\0'; + memcpy(&configJoinIp, &configJoinIp[1], MAX_CONFIG_STRING-1); + break; + } } } + + // Get host addresses + error = getaddrinfo(configJoinIp, NULL, &hints, &result); + + // If it was successful: + if (error == 0) { + // Iterate through results + for (i = result; i != NULL; i = i->ai_next) { + // buffer for IPv6 address + char str[INET6_ADDRSTRLEN]; + // IPv6 address: + if (i->ai_addr->sa_family == AF_INET6) { + struct sockaddr_in6 *p = (struct sockaddr_in6 *)i->ai_addr; + // copy address to sockaddr_in6 struct + memcpy(&addr->sin6_addr, &p->sin6_addr, sizeof(struct in6_addr)); + // set new join IP for config file + snprintf(configJoinIp, MAX_CONFIG_STRING, "%s", inet_ntop(AF_INET6, &p->sin6_addr, str, sizeof(str))); + // Free results from memory and return + freeaddrinfo(result); + return; + + } else if (i->ai_addr->sa_family == AF_INET) { // IPv4 address. Convert it to an IPv6-mapped IPv4 address so it's compatible with the IPv6 socket. + struct sockaddr_in *p = (struct sockaddr_in *)i->ai_addr; + struct in6_addr ipv6_mapped_addr; + // clear out IPv6-mapped IPv4 address buffer + memset(&ipv6_mapped_addr, 0, sizeof(struct in6_addr)); + // ::ffff: Prefix + ipv6_mapped_addr.s6_addr[10] = 0xff; + ipv6_mapped_addr.s6_addr[11] = 0xff; + // then copy the IPv4 address to the end of the IPv6 address. The address is now properly formed. + memcpy(&ipv6_mapped_addr.s6_addr[12], &p->sin_addr, sizeof(p->sin_addr)); + // copy address to sockaddr_in6 struct + memcpy(&addr->sin6_addr, &ipv6_mapped_addr, sizeof(struct in6_addr)); + + // set new join IP for config file + snprintf(configJoinIp, MAX_CONFIG_STRING, "%s", inet_ntop(AF_INET6, &ipv6_mapped_addr, str, sizeof(str))); + // Free results from memory and return + freeaddrinfo(result); + return; + } + } + } else { + LOG_ERROR("getaddrinfo() failed with error code %i: %s", error, gai_strerror(error)); + } } static int socket_bind(SOCKET socket, unsigned int port) { - struct sockaddr_in rxAddr; - rxAddr.sin_family = AF_INET; - rxAddr.sin_port = htons(port); - rxAddr.sin_addr.s_addr = htonl(INADDR_ANY); + struct sockaddr_in6 rxAddr; + // Clean struct to prevent rare cases of binding errors due to garbage data in that memory location. This just happened to me randomly on Windows and left me very confused. + memset(&rxAddr, 0, sizeof(struct sockaddr_in6)); + rxAddr.sin6_family = AF_INET6; + rxAddr.sin6_port = htons(port); + rxAddr.sin6_addr = in6addr_any; + + int rc = bind(socket, (SOCKADDR *)&rxAddr, sizeof(rxAddr)); - int rc = bind(socket, (SOCKADDR*)&rxAddr, sizeof(rxAddr)); if (rc != 0) { LOG_ERROR("bind failed with error %d", SOCKET_LAST_ERROR); } @@ -34,8 +97,8 @@ static int socket_bind(SOCKET socket, unsigned int port) { return rc; } -static int socket_send(SOCKET socket, struct sockaddr_in* addr, u8* buffer, u16 bufferLength) { - int addrSize = sizeof(struct sockaddr_in); +static int socket_send(SOCKET socket, struct sockaddr_in6* addr, u8* buffer, u16 bufferLength) { + int addrSize = sizeof(struct sockaddr_in6); int rc = sendto(socket, (char*)buffer, bufferLength, 0, (struct sockaddr*)addr, addrSize); if (rc != SOCKET_ERROR) { return NO_ERROR; } @@ -46,14 +109,14 @@ static int socket_send(SOCKET socket, struct sockaddr_in* addr, u8* buffer, u16 return rc; } -static int socket_receive(SOCKET socket, struct sockaddr_in* rxAddr, u8* buffer, u16 bufferLength, u16* receiveLength, u8* localIndex) { +static int socket_receive(SOCKET socket, struct sockaddr_in6* rxAddr, u8* buffer, u16 bufferLength, u16* receiveLength, u8* localIndex) { *receiveLength = 0; - RX_ADDR_SIZE_TYPE rxAddrSize = sizeof(struct sockaddr_in); + RX_ADDR_SIZE_TYPE rxAddrSize = sizeof(struct sockaddr_in6); int rc = recvfrom(socket, (char*)buffer, bufferLength, 0, (struct sockaddr*)rxAddr, &rxAddrSize); for (int i = 1; i < MAX_PLAYERS; i++) { - if (memcmp(rxAddr, &sAddr[i], sizeof(struct sockaddr_in)) == 0) { + if (memcmp(rxAddr, &sAddr[i], sizeof(struct sockaddr_in6)) == 0) { *localIndex = i; break; } @@ -85,30 +148,35 @@ static bool ns_socket_initialize(enum NetworkType networkType, UNUSED bool recon int reuse = 1; if (setsockopt(sCurSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0) { LOG_ERROR("setsockopt(SO_REUSEADDR) failed"); - } + } #ifdef SO_REUSEPORT if (setsockopt(sCurSocket, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0) { LOG_ERROR("setsockopt(SO_REUSEPORT) failed"); } #endif - // bind the socket to any address and the specified port. int rc = socket_bind(sCurSocket, port); - if (rc != NO_ERROR) { return false; } + if (rc != NO_ERROR) { + LOG_ERROR("bind returned an error."); + return false; + } LOG_INFO("bound to port %u", port); - } else { + } else if (networkType == NT_CLIENT) { + struct sockaddr_in6 addr; + // set and clean struct to prevent garbage data + memset(&addr, 0, sizeof(struct sockaddr_in6)); // save the port to send to - sAddr[0].sin_family = AF_INET; - sAddr[0].sin_port = htons(port); - resolve_domain(); - sAddr[0].sin_addr.s_addr = inet_addr(configJoinIp); - LOG_INFO("connecting to %s %u", configJoinIp, port); + sAddr[0].sin6_family = AF_INET6; + sAddr[0].sin6_port = htons(port); + // resolve and get address list to connect + resolve_domain(&addr); + sAddr[0].sin6_addr = addr.sin6_addr; + LOG_INFO("connecting to %s, port %u", configJoinIp, port); + // copy hostname to be saved to config file snprintf(configJoinIp, MAX_CONFIG_STRING, "%s", gGetHostName); - } - // kick off first packet - if (networkType == NT_CLIENT) { + // kick off first packet char joinText[128] = { 0 }; snprintf(joinText, 63, "%s %d", configJoinIp, configJoinPort); djui_connect_menu_open(); @@ -132,8 +200,8 @@ static s64 ns_socket_get_id(UNUSED u8 localId) { static char* ns_socket_get_id_str(u8 localId) { if (localId == UNKNOWN_LOCAL_INDEX) { localId = 0; } - static char id_str[INET_ADDRSTRLEN] = { 0 }; - snprintf(id_str, INET_ADDRSTRLEN, "%s", inet_ntoa(sAddr[localId].sin_addr)); + static char id_str[INET6_ADDRSTRLEN] = { 0 }; + snprintf(id_str, INET6_ADDRSTRLEN, "%s", inet_ntop(AF_INET6, &sAddr[localId].sin6_addr, id_str, sizeof(id_str))); return id_str; } @@ -147,18 +215,18 @@ static void ns_socket_save_id(u8 localId, UNUSED s64 networkId) { static void ns_socket_clear_id(u8 localId) { if (localId == 0) { return; } SOFT_ASSERT(localId < MAX_PLAYERS); - memset(&sAddr[localId], 0, sizeof(struct sockaddr_in)); + memset(&sAddr[localId], 0, sizeof(struct sockaddr_in6)); LOG_INFO("cleared addr for id %d", localId); } static void* ns_socket_dup_addr(u8 localIndex) { - void* address = malloc(sizeof(struct sockaddr_in)); - memcpy(address, &sAddr[localIndex], sizeof(struct sockaddr_in)); + void* address = malloc(sizeof(struct sockaddr_in6)); + memcpy(address, &sAddr[localIndex], sizeof(struct sockaddr_in6)); return address; } static bool ns_socket_match_addr(void* addr1, void* addr2) { - return !memcmp(addr1, addr2, sizeof(struct sockaddr_in)); + return !memcmp(addr1, addr2, sizeof(struct sockaddr_in6)); } static void ns_socket_update(void) { @@ -181,8 +249,8 @@ static int ns_socket_send(u8 localIndex, void* address, u8* data, u16 dataLength if (gNetworkType == NT_CLIENT && gNetworkPlayers[localIndex].type != NPT_SERVER) { return SOCKET_ERROR; } } - struct sockaddr_in* userAddr = &sAddr[localIndex]; - if (localIndex == 0 && address != NULL) { userAddr = (struct sockaddr_in*)address; } + struct sockaddr_in6* userAddr = &sAddr[localIndex]; + if (localIndex == 0 && address != NULL) { userAddr = (struct sockaddr_in6*)address; } int rc = socket_send(sCurSocket, userAddr, data, dataLength); if (rc) { @@ -203,7 +271,7 @@ static void ns_socket_shutdown(UNUSED bool reconnecting) { socket_shutdown(sCurSocket); sCurSocket = INVALID_SOCKET; for (u16 i = 0; i < MAX_PLAYERS; i++) { - memset(&sAddr[i], 0, sizeof(struct sockaddr_in)); + memset(&sAddr[i], 0, sizeof(struct sockaddr_in6)); } LOG_INFO("shutdown"); } diff --git a/src/pc/network/socket/socket_linux.c b/src/pc/network/socket/socket_linux.c index 5ab75ad0..a1903b07 100644 --- a/src/pc/network/socket/socket_linux.c +++ b/src/pc/network/socket/socket_linux.c @@ -5,7 +5,7 @@ SOCKET socket_initialize(void) { // initialize socket - SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + SOCKET sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (sock == INVALID_SOCKET) { LOG_ERROR("socket failed with error %d", SOCKET_LAST_ERROR); return INVALID_SOCKET; @@ -18,6 +18,15 @@ SOCKET socket_initialize(void) { return INVALID_SOCKET; } + // Set socket to dual-stack + int v6only = 0; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&v6only, sizeof(v6only)) < 0) { + LOG_ERROR("setsockopt(IPV6_V6ONLY) failed."); + return INVALID_SOCKET; + }; + + LOG_INFO("socket initialized."); + return sock; } diff --git a/src/pc/network/socket/socket_windows.c b/src/pc/network/socket/socket_windows.c index 635b96c3..4435848d 100644 --- a/src/pc/network/socket/socket_windows.c +++ b/src/pc/network/socket/socket_windows.c @@ -13,7 +13,7 @@ SOCKET socket_initialize(void) { } // initialize socket - SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + SOCKET sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (sock == INVALID_SOCKET) { LOG_ERROR("socket failed with error %d", SOCKET_LAST_ERROR); return INVALID_SOCKET; @@ -27,6 +27,13 @@ SOCKET socket_initialize(void) { return INVALID_SOCKET; } + // set dual-stack socket mode + int v6only = 0; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&v6only, sizeof(v6only)) < 0) { + LOG_ERROR("setsockopt(IPV6_V6ONLY) failed."); + return INVALID_SOCKET; + }; + #if MAX_PLAYERS > 4 // on windows, the send buffer for the socket needs to be increased // for the many players case to avoid WSAEWOULDBLOCK on send