From 65355994b34ee0289bd87ca05d9a105af5e677d6 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 21:00:53 -0500 Subject: [PATCH 01/12] NAT traversal Adding NAT traveral interface to libretro-common, with (currently) a backend support MiniUPNPC. Sensible future backends would be libupnp and a direct implementation of PCP/NAT-PMP. --- Makefile.common | 5 + libretro-common/include/net/net_natt.h | 47 ++++++ libretro-common/net/net_natt.c | 192 +++++++++++++++++++++++++ qb/config.libs.sh | 4 + qb/config.params.sh | 1 + 5 files changed, 249 insertions(+) create mode 100644 libretro-common/include/net/net_natt.h create mode 100644 libretro-common/net/net_natt.c diff --git a/Makefile.common b/Makefile.common index f3cfaf753a..2980615ee3 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1086,6 +1086,7 @@ ifeq ($(HAVE_NETWORKING), 1) OBJ += $(LIBRETRO_COMM_DIR)/net/net_compat.o \ $(LIBRETRO_COMM_DIR)/net/net_http.o \ $(LIBRETRO_COMM_DIR)/net/net_socket.o \ + $(LIBRETRO_COMM_DIR)/net/net_natt.o \ network/net_http_special.o \ tasks/task_http.o @@ -1123,6 +1124,10 @@ ifeq ($(HAVE_NETWORKING), 1) OBJ += input/input_remote.o \ cores/libretro-net-retropad/net_retropad_core.o endif + + ifeq ($(HAVE_MINIUPNPC), 1) + LIBS += -lminiupnpc + endif endif ifneq ($(findstring Win32,$(OS)),) diff --git a/libretro-common/include/net/net_natt.h b/libretro-common/include/net/net_natt.h new file mode 100644 index 0000000000..ec09780711 --- /dev/null +++ b/libretro-common/include/net/net_natt.h @@ -0,0 +1,47 @@ +#include + +struct natt_status { + /** The fdset to be selected upon to check for responses */ + fd_set fds; + + /** True if we've resolved an external IPv4 address */ + bool have_inet4; + + /** External IPv4 address */ + struct sockaddr_in ext_inet4_addr; + + /** True if we've resolved an external IPv6 address */ + bool have_inet6; + +#ifdef AF_INET6 + /** External IPv6 address */ + struct sockaddr_in6 ext_inet6_addr; +#endif + + /** Internal status (currently unused) */ + void *internal; +}; + +/** + * Initialize global NAT traversal structures (must be called once to use other + * functions) */ +void natt_init(void); + +/** Initialize a NAT traversal status object */ +bool natt_new(struct natt_status *status); + +/** Free a NAT traversal status object */ +void natt_free(struct natt_status *status); + +/** + * Make a port forwarding request. This may finish immediately or just send a + * request to the network. */ +bool natt_open_port(struct natt_status *status, struct sockaddr *addr, socklen_t addrlen); + +/** + * Make a port forwarding request when only the port is known. Forwards any + * address it can find. */ +bool natt_open_port_any(struct natt_status *status, uint16_t port); + +/** Check for port forwarding responses */ +bool natt_read(struct natt_status *status); diff --git a/libretro-common/net/net_natt.c b/libretro-common/net/net_natt.c new file mode 100644 index 0000000000..5f116a69cd --- /dev/null +++ b/libretro-common/net/net_natt.c @@ -0,0 +1,192 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include + +#if HAVE_MINIUPNPC +#include +#include +#include + +static struct UPNPUrls urls; +static struct IGDdatas data; +#endif + +void natt_init(void) +{ +#if HAVE_MINIUPNPC + struct UPNPDev * devlist; + struct UPNPDev * dev; + char * descXML; + int descXMLsize = 0; + int upnperror = 0; + memset(&urls, 0, sizeof(struct UPNPUrls)); + memset(&data, 0, sizeof(struct IGDdatas)); + devlist = upnpDiscover(2000, NULL, NULL, 0, 0, &upnperror); + if (devlist) + { + dev = devlist; + while (dev) + { + if (strstr (dev->st, "InternetGatewayDevice")) + break; + dev = dev->pNext; + } + if (!dev) + dev = devlist; + + descXML = miniwget(dev->descURL, &descXMLsize, 0); + if (descXML) + { + parserootdesc (descXML, descXMLsize, &data); + free (descXML); descXML = 0; + GetUPNPUrls (&urls, &data, dev->descURL, 0); + } + freeUPNPDevlist(devlist); + } +#endif +} + +bool natt_new(struct natt_status *status) +{ + memset(status, 0, sizeof(struct natt_status)); + return true; +} + +void natt_free(struct natt_status *status) +{ + /* Nothing */ +} + +bool natt_open_port(struct natt_status *status, struct sockaddr *addr, socklen_t addrlen) +{ +#if HAVE_MINIUPNPC + char host[PATH_MAX_LENGTH], ext_host[PATH_MAX_LENGTH], + port_str[6], ext_port_str[6]; + struct addrinfo hints = {0}, *ext_addrinfo; + int r; + + /* if NAT traversal is uninitialized or unavailable, oh well */ + if (!urls.controlURL[0]) + return false; + + /* figure out the internal info */ + if (getnameinfo(addr, addrlen, host, PATH_MAX_LENGTH, port_str, 6, 0) != 0) + return false; + + /* add the port mapping */ + r = UPNP_AddAnyPortMapping(urls.controlURL, data.first.servicetype, port_str, + port_str, host, "retroarch", "TCP", NULL, "3600", ext_port_str); + if (r == 501 /* Action Failed */) + { + /* try the older AddPortMapping */ + memcpy(ext_port_str, port_str, 6); + r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port_str, + port_str, host, "retroarch", "TCP", NULL, "3600"); + } + fprintf(stderr, "ERROR: %d\n", r); + if (r != 0) + return false; + + /* get the external IP */ + r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, ext_host); + if (r != 0) + return false; + + /* update the status */ + if (getaddrinfo_retro(ext_host, ext_port_str, &hints, &ext_addrinfo) != 0) + return false; + + if (ext_addrinfo->ai_family == AF_INET && + ext_addrinfo->ai_addrlen >= sizeof(struct sockaddr_in)) + { + status->have_inet4 = true; + status->ext_inet4_addr = *((struct sockaddr_in *) ext_addrinfo->ai_addr); + } +#ifdef AF_INET6 + else if (ext_addrinfo->ai_family == AF_INET6 && + ext_addrinfo->ai_addrlen >= sizeof(struct sockaddr_in6)) + { + status->have_inet6 = true; + status->ext_inet6_addr = *((struct sockaddr_in6 *) ext_addrinfo->ai_addr); + } + else + { + freeaddrinfo_retro(ext_addrinfo); + return false; + } +#endif + + return true; + +#else + return false; +#endif +} + +bool natt_open_port_any(struct natt_status *status, uint16_t port) +{ + struct net_ifinfo list; + bool ret = false; + size_t i; + struct addrinfo hints = {0}, *addr; + char port_str[6]; + + sprintf(port_str, "%hu", port); + + /* get our interfaces */ + if (!net_ifinfo_new(&list)) + return false; + + /* loop through them */ + for (i = 0; i < list.size; i++) + { + struct net_ifinfo_entry *entry = list.entries + i; + + /* ignore localhost */ + if (!strcmp(entry->host, "127.0.0.1") || !strcmp(entry->host, "::1")) + continue; + + /* make a request for this host */ + if (getaddrinfo_retro(entry->host, port_str, &hints, &addr) == 0) + { + ret = natt_open_port(status, addr->ai_addr, addr->ai_addrlen) || ret; + freeaddrinfo_retro(addr); + } + } + + /* BUGS! net_ifinfo_free(&list); */ + + return ret; +} + +bool natt_read(struct natt_status *status) +{ + /* MiniUPNPC is always synchronous, so there's nothing to read here. + * Reserved for future backends. */ + return false; +} + +#if 0 +/* If we want to remove redirects in the future, this is a sample of how to do + * that */ +void upnp_rem_redir (int port) +{ + char port_str[16]; + int t; + printf("TB : upnp_rem_redir (%d)\n", port); + if(urls.controlURL[0] == '\0') + { + printf("TB : the init was not done !\n"); + return; + } + sprintf(port_str, "%d", port); + UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port_str, "TCP", NULL); +} +#endif diff --git a/qb/config.libs.sh b/qb/config.libs.sh index 3d68a861a8..6d789eac9f 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -182,6 +182,10 @@ if [ "$HAVE_NETWORKING" = 'yes' ]; then fi HAVE_NETWORK_CMD=yes HAVE_NETWORKGAMEPAD=yes + + if [ "$HAVE_MINIUPNPC" != "no" ]; then + check_lib MINIUPNPC "-lminiupnpc" + fi else echo "Warning: All networking features have been disabled." HAVE_NETWORK_CMD='no' diff --git a/qb/config.params.sh b/qb/config.params.sh index 61d5a41861..9254edba6b 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -28,6 +28,7 @@ HAVE_DYLIB=auto # Dynamic loading support HAVE_NETWORKING=auto # Networking features (recommended) HAVE_NETWORKGAMEPAD=auto # Networked game pad (plus baked-in core) C89_NETWORKGAMEPAD=no +HAVE_MINIUPNPC=auto # Mini UPnP client library (for NAT traversal) HAVE_D3D9=yes # Direct3D 9 support HAVE_OPENGL=auto # OpenGL support HAVE_MALI_FBDEV=no # Mali fbdev context support From da406c3abb1dfef2c9cbf3149bb741ef40acc8df Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 21:09:47 -0500 Subject: [PATCH 02/12] Adding copyright headers and support for specifying protocol (TCP/UDP) --- libretro-common/include/net/net_natt.h | 34 ++++++++++++++++++++++++-- libretro-common/net/net_natt.c | 34 ++++++++++++++++++++++---- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/libretro-common/include/net/net_natt.h b/libretro-common/include/net/net_natt.h index ec09780711..a4bbd29f00 100644 --- a/libretro-common/include/net/net_natt.h +++ b/libretro-common/include/net/net_natt.h @@ -1,4 +1,30 @@ +/* Copyright (C) 2016 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (net_natt.h). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _LIBRETRO_SDK_NET_NATT_H +#define _LIBRETRO_SDK_NET_NATT_H + #include +#include struct natt_status { /** The fdset to be selected upon to check for responses */ @@ -36,12 +62,16 @@ void natt_free(struct natt_status *status); /** * Make a port forwarding request. This may finish immediately or just send a * request to the network. */ -bool natt_open_port(struct natt_status *status, struct sockaddr *addr, socklen_t addrlen); +bool natt_open_port(struct natt_status *status, struct sockaddr *addr, + socklen_t addrlen, enum socket_protocol proto); /** * Make a port forwarding request when only the port is known. Forwards any * address it can find. */ -bool natt_open_port_any(struct natt_status *status, uint16_t port); +bool natt_open_port_any(struct natt_status *status, uint16_t port, + enum socket_protocol proto); /** Check for port forwarding responses */ bool natt_read(struct natt_status *status); + +#endif diff --git a/libretro-common/net/net_natt.c b/libretro-common/net/net_natt.c index 5f116a69cd..5bb6b380ee 100644 --- a/libretro-common/net/net_natt.c +++ b/libretro-common/net/net_natt.c @@ -1,3 +1,25 @@ +/* Copyright (C) 2016 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (net_natt.c). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + #include #include #include @@ -64,11 +86,12 @@ void natt_free(struct natt_status *status) /* Nothing */ } -bool natt_open_port(struct natt_status *status, struct sockaddr *addr, socklen_t addrlen) +bool natt_open_port(struct natt_status *status, struct sockaddr *addr, socklen_t addrlen, enum socket_protocol proto) { #if HAVE_MINIUPNPC char host[PATH_MAX_LENGTH], ext_host[PATH_MAX_LENGTH], port_str[6], ext_port_str[6]; + const char *proto_str; struct addrinfo hints = {0}, *ext_addrinfo; int r; @@ -79,16 +102,17 @@ bool natt_open_port(struct natt_status *status, struct sockaddr *addr, socklen_t /* figure out the internal info */ if (getnameinfo(addr, addrlen, host, PATH_MAX_LENGTH, port_str, 6, 0) != 0) return false; + proto_str = (proto == SOCKET_PROTOCOL_UDP) ? "UDP" : "TCP"; /* add the port mapping */ r = UPNP_AddAnyPortMapping(urls.controlURL, data.first.servicetype, port_str, - port_str, host, "retroarch", "TCP", NULL, "3600", ext_port_str); + port_str, host, "retroarch", proto_str, NULL, "3600", ext_port_str); if (r == 501 /* Action Failed */) { /* try the older AddPortMapping */ memcpy(ext_port_str, port_str, 6); r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port_str, - port_str, host, "retroarch", "TCP", NULL, "3600"); + port_str, host, "retroarch", proto_str, NULL, "3600"); } fprintf(stderr, "ERROR: %d\n", r); if (r != 0) @@ -130,7 +154,7 @@ bool natt_open_port(struct natt_status *status, struct sockaddr *addr, socklen_t #endif } -bool natt_open_port_any(struct natt_status *status, uint16_t port) +bool natt_open_port_any(struct natt_status *status, uint16_t port, enum socket_protocol proto) { struct net_ifinfo list; bool ret = false; @@ -156,7 +180,7 @@ bool natt_open_port_any(struct natt_status *status, uint16_t port) /* make a request for this host */ if (getaddrinfo_retro(entry->host, port_str, &hints, &addr) == 0) { - ret = natt_open_port(status, addr->ai_addr, addr->ai_addrlen) || ret; + ret = natt_open_port(status, addr->ai_addr, addr->ai_addrlen, proto) || ret; freeaddrinfo_retro(addr); } } From 8f61e2b9788dcb3bf57e9b89fdb2d3da778d7011 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 21:16:04 -0500 Subject: [PATCH 03/12] Spacing correction (three spaces, not four) --- libretro-common/include/net/net_natt.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/libretro-common/include/net/net_natt.h b/libretro-common/include/net/net_natt.h index a4bbd29f00..f50f6fa45f 100644 --- a/libretro-common/include/net/net_natt.h +++ b/libretro-common/include/net/net_natt.h @@ -27,25 +27,25 @@ #include struct natt_status { - /** The fdset to be selected upon to check for responses */ - fd_set fds; + /** The fdset to be selected upon to check for responses */ + fd_set fds; - /** True if we've resolved an external IPv4 address */ - bool have_inet4; + /** True if we've resolved an external IPv4 address */ + bool have_inet4; - /** External IPv4 address */ - struct sockaddr_in ext_inet4_addr; + /** External IPv4 address */ + struct sockaddr_in ext_inet4_addr; - /** True if we've resolved an external IPv6 address */ - bool have_inet6; + /** True if we've resolved an external IPv6 address */ + bool have_inet6; #ifdef AF_INET6 - /** External IPv6 address */ - struct sockaddr_in6 ext_inet6_addr; + /** External IPv6 address */ + struct sockaddr_in6 ext_inet6_addr; #endif - /** Internal status (currently unused) */ - void *internal; + /** Internal status (currently unused) */ + void *internal; }; /** From 50ec1bc9cdc07350481eadb98c30eb61843ad9b5 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 21:16:25 -0500 Subject: [PATCH 04/12] Add NAT traversal code to griffin --- griffin/griffin.c | 1 + 1 file changed, 1 insertion(+) diff --git a/griffin/griffin.c b/griffin/griffin.c index b57420f4a5..4785559281 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -879,6 +879,7 @@ NETPLAY #include "../libretro-common/net/net_compat.c" #include "../libretro-common/net/net_socket.c" #include "../libretro-common/net/net_http.c" +#include "../libretro-common/net/net_nett.c" #ifndef HAVE_SOCKET_LEGACY #include "../libretro-common/net/net_ifinfo.c" #endif From 48240c2806bd612f716440d842676c64fb0c402a Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 22:18:37 -0500 Subject: [PATCH 05/12] Adding Netplay NAT traversal option (but not implementation yet) --- configuration.c | 3 ++- configuration.h | 1 + intl/msg_hash_us.c | 6 ++++++ intl/msg_hash_us.h | 2 ++ menu/menu_displaylist.c | 4 ++++ menu/menu_setting.c | 15 +++++++++++++++ msg_hash.h | 1 + 7 files changed, 31 insertions(+), 1 deletion(-) diff --git a/configuration.c b/configuration.c index 9256fde0b9..e1e6964bdd 100644 --- a/configuration.c +++ b/configuration.c @@ -799,6 +799,7 @@ static int populate_settings_bool(settings_t *settings, struct config_bool_setti #endif #ifdef HAVE_NETWORKING SETTING_BOOL("netplay_spectator_mode_enable",&settings->netplay.is_spectate, false, false /* TODO */, false); + SETTING_BOOL("netplay_nat_traversal", &settings->netplay.nat_traversal, true, true, false); #endif SETTING_BOOL("block_sram_overwrite", &settings->block_sram_overwrite, true, block_sram_overwrite, false); SETTING_BOOL("savestate_auto_index", &settings->savestate_auto_index, true, savestate_auto_index, false); @@ -921,7 +922,7 @@ static int populate_settings_int(settings_t *settings, struct config_int_setting #ifdef HAVE_NETWORKING SETTING_INT("netplay_ip_port", &settings->netplay.port, false, 0 /* TODO */, false); SETTING_INT("netplay_delay_frames", &settings->netplay.sync_frames, true, 16, false); - SETTING_INT("netplay_check_frames", &settings->netplay.check_frames, false, 30, false); + SETTING_INT("netplay_check_frames", &settings->netplay.check_frames, true, 30, false); #endif #ifdef HAVE_LANGEXTRA SETTING_INT("user_language", &settings->user_language, true, RETRO_LANGUAGE_ENGLISH, false); diff --git a/configuration.h b/configuration.h index bef8bbb67e..10ad4b7d17 100644 --- a/configuration.h +++ b/configuration.h @@ -403,6 +403,7 @@ typedef struct settings unsigned check_frames; bool is_spectate; bool swap_input; + bool nat_traversal; } netplay; #endif diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index d5abdde020..e15ac8cee9 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -1548,6 +1548,12 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) "no checks. This value is only used on the \n" "netplay host. \n"); break; + case MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL: + snprintf(s, len, + "When hosting, attempt to listen for\n" + "connections from the public internet, using\n" + "UPnP or similar technologies to escape LANs. \n"); + break; case MENU_ENUM_LABEL_VIDEO_MAX_SWAPCHAIN_IMAGES: snprintf(s, len, "Maximum amount of swapchain images. This \n" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 8240988031..a70808f908 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -920,6 +920,8 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATOR_MODE_ENABLE, "Netplay Spectator Enable") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_TCP_UDP_PORT, "Netplay TCP/UDP Port") +MSG_HASH(MENU_ENUM_LABEL_VALUE_NETPLAY_NAT_TRAVERSAL, + "Netplay NAT Traversal") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETWORK_CMD_ENABLE, "Network Commands") MSG_HASH(MENU_ENUM_LABEL_VALUE_NETWORK_CMD_PORT, diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 2a3868681f..4d0e99f658 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -4942,6 +4942,10 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) MENU_ENUM_LABEL_NETPLAY_CLIENT_SWAP_INPUT, PARSE_ONLY_BOOL, false) != -1) count++; + if (menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL, + PARSE_ONLY_BOOL, false) != -1) + count++; if (menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_NETWORK_CMD_ENABLE, PARSE_ONLY_BOOL, false) != -1) diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 651f4a2aa5..3dec0627f0 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -5583,6 +5583,21 @@ static bool setting_append_list( general_read_handler, SD_FLAG_NONE); + CONFIG_BOOL( + list, list_info, + &settings->netplay.is_spectate, + MENU_ENUM_LABEL_NETPLAY_NAT_TRAVERSAL, + MENU_ENUM_LABEL_VALUE_NETPLAY_NAT_TRAVERSAL, + false, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); + CONFIG_BOOL( list, list_info, &settings->netplay.swap_input, diff --git a/msg_hash.h b/msg_hash.h index 8e5477a7c0..702453f0ec 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -974,6 +974,7 @@ enum msg_hash_enums MENU_LABEL(NETPLAY_CHECK_FRAMES), MENU_LABEL(NETPLAY_SPECTATOR_MODE_ENABLE), MENU_LABEL(NETPLAY_TCP_UDP_PORT), + MENU_LABEL(NETPLAY_NAT_TRAVERSAL), MENU_LABEL(SORT_SAVEFILES_ENABLE), MENU_LABEL(SORT_SAVESTATES_ENABLE), MENU_LABEL(NETPLAY_IP_ADDRESS), From 42da0a01845be4769b699ba1965ae8dc491cf56a Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 22:59:46 -0500 Subject: [PATCH 06/12] NAT traversal in Netplay For the time being, if NAT traversal is successful it simply announces it as an OSD message. In the future it will be used to inform a matchmaking server of the public port. This patch also included minor fixes to the NAT traversal implementation to make the select it demands actually doable. --- intl/msg_hash_us.h | 4 ++ libretro-common/include/net/net_natt.h | 6 ++ msg_hash.h | 1 + network/netplay/netplay.c | 83 ++++++++++++++++++++++++-- network/netplay/netplay.h | 3 +- network/netplay/netplay_net.c | 14 +++++ network/netplay/netplay_private.h | 4 ++ 7 files changed, 108 insertions(+), 7 deletions(-) diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index a70808f908..9e865016d2 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -22,6 +22,10 @@ MSG_HASH( MSG_GOT_CONNECTION_FROM, "Got connection from" ) +MSG_HASH( + MSG_PUBLIC_ADDRESS, + "Public address" + ) MSG_HASH( MSG_NO_ARGUMENTS_SUPPLIED_AND_NO_MENU_BUILTIN, "No arguments supplied and no menu builtin, displaying help..." diff --git a/libretro-common/include/net/net_natt.h b/libretro-common/include/net/net_natt.h index f50f6fa45f..bb9c791f61 100644 --- a/libretro-common/include/net/net_natt.h +++ b/libretro-common/include/net/net_natt.h @@ -27,9 +27,15 @@ #include struct natt_status { + /** nfds for select when checking for input */ + int nfds; + /** The fdset to be selected upon to check for responses */ fd_set fds; + /** True if there might be a request outstanding */ + bool request_outstanding; + /** True if we've resolved an external IPv4 address */ bool have_inet4; diff --git a/msg_hash.h b/msg_hash.h index 702453f0ec..089559365e 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -173,6 +173,7 @@ enum msg_hash_enums MSG_NO_STATE_HAS_BEEN_LOADED_YET, MSG_GOT_CONNECTION_FROM, MSG_CONNECTION_SLOT, + MSG_PUBLIC_ADDRESS, MSG_NO_SAVE_STATE_HAS_BEEN_OVERWRITTEN_YET, MSG_CANNOT_INFER_NEW_CONFIG_PATH, MSG_UNDID_LOAD_STATE, diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 074b70e6ea..b2d33088f3 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -63,6 +63,8 @@ static netplay_t *netplay_data = NULL; /* Used to avoid recursive netplay calls */ static bool in_netplay = false; +static void announce_nat_traversal(netplay_t *netplay); + static int init_tcp_connection(const struct addrinfo *res, bool server, bool spectate, struct sockaddr *other_addr, socklen_t addr_size) @@ -172,6 +174,22 @@ static bool init_tcp_socket(netplay_t *netplay, const char *server, return ret; } +static void init_nat_traversal(netplay_t *netplay) +{ + natt_init(); + + if (!natt_new(&netplay->nat_traversal_state)) + { + netplay->nat_traversal = false; + return; + } + + natt_open_port_any(&netplay->nat_traversal_state, netplay->tcp_port, SOCKET_PROTOCOL_TCP); + + if (!netplay->nat_traversal_state.request_outstanding) + announce_nat_traversal(netplay); +} + static bool init_ad_socket(netplay_t *netplay, uint16_t port) { int fd = socket_init((void**)&netplay->addr, port, NULL, SOCKET_TYPE_DATAGRAM); @@ -202,6 +220,9 @@ static bool init_socket(netplay_t *netplay, const char *server, uint16_t port) if (!init_tcp_socket(netplay, server, port, netplay->spectate.enabled)) return false; + if (netplay->is_server && netplay->nat_traversal) + init_nat_traversal(netplay); + return true; } @@ -1047,6 +1068,36 @@ void netplay_log_connection(const struct sockaddr_storage *their_addr, #endif +static void announce_nat_traversal(netplay_t *netplay) +{ + char msg[512], host[PATH_MAX_LENGTH], port[6]; + + if (netplay->nat_traversal_state.have_inet4) + { + if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet4_addr, + sizeof(struct sockaddr_in), + host, PATH_MAX_LENGTH, port, 6, 0) != 0) + return; + + } +#ifdef AF_INET6 + else if (netplay->nat_traversal_state.have_inet6) + { + if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet6_addr, + sizeof(struct sockaddr_in6), + host, PATH_MAX_LENGTH, port, 6, 0) != 0) + return; + + } +#endif + else return; + + snprintf(msg, sizeof(msg), "%s: %s:%s\n", + msg_hash_to_str(MSG_PUBLIC_ADDRESS), + host, port); + runloop_msg_queue_push(msg, 1, 180, false); + RARCH_LOG("%s\n", msg); +} bool netplay_try_init_serialization(netplay_t *netplay) @@ -1166,6 +1217,7 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. * @spectate : If true, enable spectator mode. + * @nat_traversal : If true, attempt NAT traversal. * @nick : Nickname of user. * @quirks : Netplay quirks required for this session. * @@ -1174,10 +1226,9 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) * * Returns: new netplay handle. **/ -netplay_t *netplay_new(const char *server, uint16_t port, - unsigned frames, unsigned check_frames, - const struct retro_callbacks *cb, - bool spectate, const char *nick, uint64_t quirks) +netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, + unsigned check_frames, const struct retro_callbacks *cb, bool spectate, + bool nat_traversal, const char *nick, uint64_t quirks) { netplay_t *netplay = (netplay_t*)calloc(1, sizeof(*netplay)); if (!netplay) @@ -1189,6 +1240,7 @@ netplay_t *netplay_new(const char *server, uint16_t port, netplay->port = server ? 0 : 1; netplay->spectate.enabled = spectate; netplay->is_server = server == NULL; + netplay->nat_traversal = netplay->is_server ? nat_traversal : false; netplay->stall_frames = frames; netplay->check_frames = check_frames; netplay->quirks = quirks; @@ -1324,6 +1376,9 @@ void netplay_free(netplay_t *netplay) free(netplay->spectate.input); } + if (netplay->nat_traversal) + natt_free(&netplay->nat_traversal_state); + if (netplay->buffer) { for (i = 0; i < netplay->buffer_size; i++) @@ -1369,11 +1424,26 @@ bool netplay_pre_frame(netplay_t *netplay) netplay_try_init_serialization(netplay); } - /* Advertise our server if applicable */ if (netplay->is_server) { + /* Advertise our server if applicable */ if (netplay_ad_fd >= 0 || init_ad_socket(netplay, RARCH_DEFAULT_PORT)) netplay_ad_server(netplay, netplay_ad_fd); + + /* NAT traversal if applicable */ + if (netplay->nat_traversal && + netplay->nat_traversal_state.request_outstanding && + !netplay->nat_traversal_state.have_inet4) + { + struct timeval tmptv = {0}; + fd_set fds = netplay->nat_traversal_state.fds; + if (socket_select(netplay->nat_traversal_state.nfds, &fds, NULL, NULL, &tmptv) > 0) + natt_read(&netplay->nat_traversal_state); + + if (!netplay->nat_traversal_state.request_outstanding || + netplay->nat_traversal_state.have_inet4) + announce_nat_traversal(netplay); + } } if (!netplay->net_cbs->pre_frame(netplay)) @@ -1599,7 +1669,8 @@ bool init_netplay(bool is_spectate, const char *server, unsigned port) netplay_is_client ? server : NULL, port ? port : RARCH_DEFAULT_PORT, settings->netplay.sync_frames, settings->netplay.check_frames, &cbs, - is_spectate, settings->username, quirks); + is_spectate, settings->netplay.nat_traversal, settings->username, + quirks); if (netplay_data) return true; diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 83b41b69a1..b1470bb96b 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -137,6 +137,7 @@ size_t audio_sample_batch_net(const int16_t *data, size_t frames); * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. * @spectate : If true, enable spectator mode. + * @nat_traversal : If true, attempt NAT traversal. * @nick : Nickname of user. * @quirks : Netplay quirks. * @@ -147,7 +148,7 @@ size_t audio_sample_batch_net(const int16_t *data, size_t frames); **/ netplay_t *netplay_new(const char *server, uint16_t port, unsigned frames, unsigned check_frames, - const struct retro_callbacks *cb, bool spectate, + const struct retro_callbacks *cb, bool spectate, bool nat_traversal, const char *nick, uint64_t quirks); /** diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index c594587e0f..cc6e409fc6 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -20,6 +20,7 @@ #include #include +#include #include "netplay_private.h" @@ -337,6 +338,19 @@ static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames) netplay->has_connection = true; } + { + struct natt_status status; + natt_init(); + if (natt_new(&status) && natt_open_port_any(&status, netplay->tcp_port, SOCKET_PROTOCOL_TCP)) + { + fprintf(stderr, "Forwarded to %d!\n", status.ext_inet4_addr.sin_port); + } + else + { + fprintf(stderr, "Forwarding failed :(\n"); + } + } + return true; } diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 40689561c0..9318a70681 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -20,6 +20,7 @@ #include "netplay.h" #include +#include #include #include #include @@ -124,6 +125,9 @@ struct netplay int fd; /* TCP port (if serving) */ uint16_t tcp_port; + /* NAT traversal info (if NAT traversal is used and serving) */ + bool nat_traversal; + struct natt_status nat_traversal_state; /* Which port is governed by netplay (other user)? */ unsigned port; bool has_connection; From c342c103aed089345182bdeee2633520e594272b Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 23:08:26 -0500 Subject: [PATCH 07/12] Always get numeric host/ports instead of names. --- libretro-common/net/net_natt.c | 2 +- network/netplay/netplay.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libretro-common/net/net_natt.c b/libretro-common/net/net_natt.c index 5bb6b380ee..6c37fa225d 100644 --- a/libretro-common/net/net_natt.c +++ b/libretro-common/net/net_natt.c @@ -100,7 +100,7 @@ bool natt_open_port(struct natt_status *status, struct sockaddr *addr, socklen_t return false; /* figure out the internal info */ - if (getnameinfo(addr, addrlen, host, PATH_MAX_LENGTH, port_str, 6, 0) != 0) + if (getnameinfo(addr, addrlen, host, PATH_MAX_LENGTH, port_str, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0) return false; proto_str = (proto == SOCKET_PROTOCOL_UDP) ? "UDP" : "TCP"; diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index b2d33088f3..5bf7bdaf58 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -1076,7 +1076,7 @@ static void announce_nat_traversal(netplay_t *netplay) { if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet4_addr, sizeof(struct sockaddr_in), - host, PATH_MAX_LENGTH, port, 6, 0) != 0) + host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0) return; } @@ -1085,7 +1085,7 @@ static void announce_nat_traversal(netplay_t *netplay) { if (getnameinfo((const struct sockaddr *) &netplay->nat_traversal_state.ext_inet6_addr, sizeof(struct sockaddr_in6), - host, PATH_MAX_LENGTH, port, 6, 0) != 0) + host, PATH_MAX_LENGTH, port, 6, NI_NUMERICHOST|NI_NUMERICSERV) != 0) return; } From 025993231fb6f09b2fed1d4c2dcda862b6c5ea5e Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 23:08:48 -0500 Subject: [PATCH 08/12] Use net_ifinfo how it's intended (?) to be used --- libretro-common/net/net_natt.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libretro-common/net/net_natt.c b/libretro-common/net/net_natt.c index 6c37fa225d..2fb9dbe2eb 100644 --- a/libretro-common/net/net_natt.c +++ b/libretro-common/net/net_natt.c @@ -156,7 +156,7 @@ bool natt_open_port(struct natt_status *status, struct sockaddr *addr, socklen_t bool natt_open_port_any(struct natt_status *status, uint16_t port, enum socket_protocol proto) { - struct net_ifinfo list; + struct net_ifinfo *list; bool ret = false; size_t i; struct addrinfo hints = {0}, *addr; @@ -165,13 +165,15 @@ bool natt_open_port_any(struct natt_status *status, uint16_t port, enum socket_p sprintf(port_str, "%hu", port); /* get our interfaces */ - if (!net_ifinfo_new(&list)) + if ((list = calloc(1, sizeof(struct net_ifinfo))) == NULL) + return false; + if (!net_ifinfo_new(list)) return false; /* loop through them */ - for (i = 0; i < list.size; i++) + for (i = 0; i < list->size; i++) { - struct net_ifinfo_entry *entry = list.entries + i; + struct net_ifinfo_entry *entry = list->entries + i; /* ignore localhost */ if (!strcmp(entry->host, "127.0.0.1") || !strcmp(entry->host, "::1")) @@ -185,7 +187,8 @@ bool natt_open_port_any(struct natt_status *status, uint16_t port, enum socket_p } } - /* BUGS! net_ifinfo_free(&list); */ + /* This really shouldn't free list, but does */ + net_ifinfo_free(list); return ret; } From 2522f688707a5cf72d3a60be62039d1f6c897fbb Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 23:10:19 -0500 Subject: [PATCH 09/12] C++ fixes --- libretro-common/net/net_natt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libretro-common/net/net_natt.c b/libretro-common/net/net_natt.c index 2fb9dbe2eb..ea3a32a539 100644 --- a/libretro-common/net/net_natt.c +++ b/libretro-common/net/net_natt.c @@ -63,7 +63,7 @@ void natt_init(void) if (!dev) dev = devlist; - descXML = miniwget(dev->descURL, &descXMLsize, 0); + descXML = (char *) miniwget(dev->descURL, &descXMLsize, 0); if (descXML) { parserootdesc (descXML, descXMLsize, &data); @@ -165,7 +165,7 @@ bool natt_open_port_any(struct natt_status *status, uint16_t port, enum socket_p sprintf(port_str, "%hu", port); /* get our interfaces */ - if ((list = calloc(1, sizeof(struct net_ifinfo))) == NULL) + if ((list = (struct net_ifinfo *) calloc(1, sizeof(struct net_ifinfo))) == NULL) return false; if (!net_ifinfo_new(list)) return false; From 0b13f7dc4a93fa7719b81cb95639c9e11af026c2 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 23:26:53 -0500 Subject: [PATCH 10/12] Tpyo in griffin include of net_natt --- griffin/griffin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/griffin/griffin.c b/griffin/griffin.c index 4785559281..80de9a08b5 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -879,7 +879,7 @@ NETPLAY #include "../libretro-common/net/net_compat.c" #include "../libretro-common/net/net_socket.c" #include "../libretro-common/net/net_http.c" -#include "../libretro-common/net/net_nett.c" +#include "../libretro-common/net/net_natt.c" #ifndef HAVE_SOCKET_LEGACY #include "../libretro-common/net/net_ifinfo.c" #endif From 8e5e760773bc20e528663129d205f84aea9fd879 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 23:30:12 -0500 Subject: [PATCH 11/12] Remove accidentally-left debug printf --- libretro-common/net/net_natt.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libretro-common/net/net_natt.c b/libretro-common/net/net_natt.c index ea3a32a539..a3f28534aa 100644 --- a/libretro-common/net/net_natt.c +++ b/libretro-common/net/net_natt.c @@ -114,7 +114,6 @@ bool natt_open_port(struct natt_status *status, struct sockaddr *addr, socklen_t r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port_str, port_str, host, "retroarch", proto_str, NULL, "3600"); } - fprintf(stderr, "ERROR: %d\n", r); if (r != 0) return false; From b1f9ef6073b3ca1820d23f0b600c2e5ab227435e Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 23:31:35 -0500 Subject: [PATCH 12/12] Since I've allocated net_ifinfo, I'd better free it in all returns --- libretro-common/net/net_natt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libretro-common/net/net_natt.c b/libretro-common/net/net_natt.c index a3f28534aa..2bd7d1f865 100644 --- a/libretro-common/net/net_natt.c +++ b/libretro-common/net/net_natt.c @@ -167,7 +167,10 @@ bool natt_open_port_any(struct natt_status *status, uint16_t port, enum socket_p if ((list = (struct net_ifinfo *) calloc(1, sizeof(struct net_ifinfo))) == NULL) return false; if (!net_ifinfo_new(list)) + { + free(list); return false; + } /* loop through them */ for (i = 0; i < list->size; i++)