From 42da0a01845be4769b699ba1965ae8dc491cf56a Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 29 Nov 2016 22:59:46 -0500 Subject: [PATCH] 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;