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/configuration.c b/configuration.c index c727ed3dd5..9284821974 100644 --- a/configuration.c +++ b/configuration.c @@ -800,6 +800,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); @@ -922,7 +923,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 fc52188ea4..861143a3dd 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/griffin/griffin.c b/griffin/griffin.c index b57420f4a5..80de9a08b5 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_natt.c" #ifndef HAVE_SOCKET_LEGACY #include "../libretro-common/net/net_ifinfo.c" #endif diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index 1379a72a7d..eb815ad7e9 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -1558,6 +1558,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 41c5878965..83ae437b73 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..." @@ -922,6 +926,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/libretro-common/include/net/net_natt.h b/libretro-common/include/net/net_natt.h new file mode 100644 index 0000000000..bb9c791f61 --- /dev/null +++ b/libretro-common/include/net/net_natt.h @@ -0,0 +1,83 @@ +/* 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 { + /** 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; + + /** 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, 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, + 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 new file mode 100644 index 0000000000..2bd7d1f865 --- /dev/null +++ b/libretro-common/net/net_natt.c @@ -0,0 +1,221 @@ +/* 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 +#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 = (char *) 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, 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; + + /* 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, NI_NUMERICHOST|NI_NUMERICSERV) != 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", 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", proto_str, NULL, "3600"); + } + 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, enum socket_protocol proto) +{ + 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 ((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++) + { + 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, proto) || ret; + freeaddrinfo_retro(addr); + } + } + + /* This really shouldn't free list, but does */ + 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/menu/menu_displaylist.c b/menu/menu_displaylist.c index f3975acdaa..c180123bb3 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 6f334610ac..bdfca9c6c6 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -5589,6 +5589,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 0ba1f8f489..c9c3f1c4aa 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, @@ -975,6 +976,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), diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 3fcea60f06..a5916c249d 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) @@ -186,6 +188,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); @@ -216,6 +234,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; } @@ -1061,6 +1082,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, NI_NUMERICHOST|NI_NUMERICSERV) != 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, NI_NUMERICHOST|NI_NUMERICSERV) != 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) @@ -1180,6 +1231,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. * @@ -1188,10 +1240,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) @@ -1203,6 +1254,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; @@ -1338,6 +1390,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++) @@ -1383,11 +1438,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)) @@ -1613,7 +1683,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; 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 202e158b27..3a338d8872 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