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.
This commit is contained in:
roddy 2024-09-01 13:48:24 -03:00 committed by GitHub
parent de47af0461
commit db3a7e3483
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 192 additions and 63 deletions

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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]

View file

@ -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;

View file

@ -98,16 +98,60 @@ 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';
// 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') {
@ -115,8 +159,9 @@ static void djui_panel_join_direct_ip_text_set_new(void) {
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");
}

View file

@ -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;
}
@ -92,23 +155,28 @@ static bool ns_socket_initialize(enum NetworkType networkType, UNUSED bool recon
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; }
LOG_INFO("bound to port %u", port);
} else {
// 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);
snprintf(configJoinIp, MAX_CONFIG_STRING, "%s", gGetHostName);
if (rc != NO_ERROR) {
LOG_ERROR("bind returned an error.");
return false;
}
LOG_INFO("bound to port %u", port);
} 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].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) {
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");
}

View file

@ -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;
}

View file

@ -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