From 4c18cec752ca86550c5301fc445f8f18d79ff1c6 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Fri, 2 Dec 2016 19:49:42 -0500 Subject: [PATCH] Added Netplay discovery code (not yet in menu) --- Makefile.common | 1 + network/netplay/netplay.c | 30 +-- network/netplay/netplay_common.c | 101 --------- network/netplay/netplay_discovery.c | 321 ++++++++++++++++++++++++++++ network/netplay/netplay_private.h | 4 +- 5 files changed, 327 insertions(+), 130 deletions(-) create mode 100644 network/netplay/netplay_discovery.c diff --git a/Makefile.common b/Makefile.common index 40fe16f938..7de2ceb6ad 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1121,6 +1121,7 @@ ifeq ($(HAVE_NETWORKING), 1) OBJ += network/netplay/netplay_net.o \ network/netplay/netplay_spectate.o \ network/netplay/netplay_common.o \ + network/netplay/netplay_discovery.o \ network/netplay/netplay.o # Retro Achievements (also depends on threads) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index c63285be71..8c0e25270e 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -54,9 +54,6 @@ enum static bool netplay_enabled = false; static bool netplay_is_client = false; -/* Used to advertise or request advertisement of Netplay */ -static int netplay_ad_fd = -1; - /* Used while Netplay is running */ static netplay_t *netplay_data = NULL; @@ -204,28 +201,6 @@ static void init_nat_traversal(netplay_t *netplay) 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); - - if (fd < 0) - goto error; - - if (!socket_bind(fd, (void*)netplay->addr)) - { - socket_close(fd); - goto error; - } - - netplay_ad_fd = fd; - - return true; - -error: - RARCH_ERR("Failed to initialize netplay advertisement socket.\n"); - return false; -} - static bool init_socket(netplay_t *netplay, const char *server, uint16_t port) { if (!network_init()) @@ -1446,9 +1421,8 @@ bool netplay_pre_frame(netplay_t *netplay) 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); + /* Advertise our server */ + netplay_lan_ad_server(netplay); /* NAT traversal if applicable */ if (netplay->nat_traversal && diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 67b07a70d8..16779d54d7 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -420,104 +420,3 @@ uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta) return 0; return encoding_crc32(0L, (const unsigned char*)delta->state, netplay->state_size); } - -/* - * AD PACKET FORMAT: - * - * Request: - * 1 word: RANQ (RetroArch Netplay Query) - * 1 word: Netplay protocol version - * - * Reply: - * 1 word : RANS (RetroArch Netplay Server) - * 1 word : Netplay protocol version - * 1 word : Port - * 8 words: RetroArch version - * 8 words: Nick - * 8 words: Core name - * 8 words: Core version - * 8 words: Content name (currently always blank) - */ - -#define AD_PACKET_MAX_SIZE 512 -#define AD_PACKET_STRING_SIZE 32 -#define AD_PACKET_STRING_WORDS (AD_PACKET_STRING_SIZE/sizeof(uint32_t)) -static uint32_t *ad_packet_buffer = NULL; - -bool netplay_ad_server(netplay_t *netplay, int ad_fd) -{ - fd_set fds; - struct timeval tmp_tv = {0}; - struct sockaddr their_addr; - socklen_t addr_size; - rarch_system_info_t *info = NULL; - size_t bufloc; - - if (!ad_packet_buffer) - { - ad_packet_buffer = (uint32_t *) malloc(AD_PACKET_MAX_SIZE); - if (!ad_packet_buffer) - return false; - } - - /* Check for any ad queries */ - while (1) - { - FD_ZERO(&fds); - FD_SET(ad_fd, &fds); - if (socket_select(ad_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) - break; - if (!FD_ISSET(ad_fd, &fds)) - break; - - /* Somebody queried, so check that it's valid */ - addr_size = sizeof(their_addr); - if (recvfrom(ad_fd, (char*)ad_packet_buffer, AD_PACKET_MAX_SIZE, 0, - &their_addr, &addr_size) >= (ssize_t) (2*sizeof(uint32_t))) - { - /* Make sure it's a valid query */ - if (memcmp(ad_packet_buffer, "RANQ", 4)) - continue; - - /* For this version */ - if (ntohl(ad_packet_buffer[1]) != NETPLAY_PROTOCOL_VERSION) - continue; - - runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); - - /* Now build our response */ - memset(ad_packet_buffer, 0, AD_PACKET_MAX_SIZE); - memcpy(ad_packet_buffer, "RANS", 4); - ad_packet_buffer[1] = htonl(NETPLAY_PROTOCOL_VERSION); - ad_packet_buffer[2] = htonl(netplay->tcp_port); - bufloc = 3; - strncpy((char *) (ad_packet_buffer + bufloc), - PACKAGE_VERSION, AD_PACKET_STRING_SIZE); - bufloc += AD_PACKET_STRING_WORDS; - strncpy((char *) (ad_packet_buffer + bufloc), - netplay->nick, AD_PACKET_STRING_SIZE); - bufloc += AD_PACKET_STRING_WORDS; - if (info) - { - strncpy((char *) (ad_packet_buffer + bufloc), - info->info.library_name, AD_PACKET_STRING_SIZE); - bufloc += AD_PACKET_STRING_WORDS; - strncpy((char *) (ad_packet_buffer + bufloc), - info->info.library_version, AD_PACKET_STRING_SIZE); - bufloc += AD_PACKET_STRING_WORDS; - /* Blank content */ - bufloc += AD_PACKET_STRING_WORDS; - } - else - { - bufloc += 3*AD_PACKET_STRING_WORDS; - } - - /* And send it */ - sendto(ad_fd, (const char*)ad_packet_buffer, bufloc*sizeof(uint32_t), 0, - &their_addr, addr_size); - } - } - - return true; -} diff --git a/network/netplay/netplay_discovery.c b/network/netplay/netplay_discovery.c new file mode 100644 index 0000000000..9074609ebf --- /dev/null +++ b/network/netplay/netplay_discovery.c @@ -0,0 +1,321 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2016 - Gregor Richards + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +/* + * AD PACKET FORMAT: + * + * Request: + * 1 word: RANQ (RetroArch Netplay Query) + * 1 word: Netplay protocol version + * + * Reply: + * 1 word : RANS (RetroArch Netplay Server) + * 1 word : Netplay protocol version + * 1 word : Port + * 8 words: RetroArch version + * 8 words: Nick + * 8 words: Core name + * 8 words: Core version + * 8 words: Content name (currently always blank) + */ + +#include +#include + +#include + +#include "../../runloop.h" +#include "../../version.h" +#include "netplay.h" +#include "netplay_discovery.h" +#include "netplay_private.h" + +struct ad_packet +{ + uint32_t header; + uint32_t protocol_version; + uint32_t port; + char retroarch_version[NETPLAY_HOST_STR_LEN]; + char nick[NETPLAY_HOST_STR_LEN]; + char core[NETPLAY_HOST_STR_LEN]; + char core_version[NETPLAY_HOST_STR_LEN]; + char content[NETPLAY_HOST_STR_LEN]; +}; + +bool netplay_lan_ad_client(void); + +/* LAN discovery sockets */ +static int lan_ad_server_fd = -1; +static int lan_ad_client_fd = -1; + +/* Packet buffer for advertisement and responses */ +static struct ad_packet ad_packet_buffer; + +/* List of discovered hosts */ +static struct netplay_host_list discovered_hosts; +static size_t discovered_hosts_allocated; + +/** Initialize Netplay discovery (client) */ +bool init_netplay_discovery(void) +{ + struct addrinfo *addr = NULL; + int fd = socket_init((void **) &addr, 0, NULL, SOCKET_TYPE_DATAGRAM); + + if (fd < 0) + goto error; + + if (!socket_bind(fd, (void*)addr)) + { + socket_close(fd); + goto error; + } + + lan_ad_client_fd = fd; + freeaddrinfo_retro(addr); + return true; + +error: + if (addr) + freeaddrinfo_retro(addr); + RARCH_ERR("Failed to initialize netplay advertisement client socket.\n"); + return false; +} + +/** Deinitialize and free Netplay discovery */ +void deinit_netplay_discovery(void) +{ + if (lan_ad_client_fd >= 0) + { + socket_close(lan_ad_client_fd); + lan_ad_client_fd = -1; + } +} + +/** Discovery control */ +bool netplay_discovery_driver_ctl(enum rarch_netplay_discovery_ctl_state state, void *data) +{ + char port_str[6]; + + if (lan_ad_client_fd < 0) + return false; + + switch (state) + { + case RARCH_NETPLAY_DISCOVERY_CTL_LAN_SEND_QUERY: + { + struct addrinfo hints = {0}, *addr; + + /* Get the broadcast address (IPv4 only for now) */ + snprintf(port_str, 6, "%hu", RARCH_DEFAULT_PORT); + if (getaddrinfo_retro("255.255.255.255", port_str, &hints, &addr) < 0) + return false; + + /* Put together the request */ + memcpy((void *) &ad_packet_buffer, "RANQ", 4); + ad_packet_buffer.protocol_version = htonl(NETPLAY_PROTOCOL_VERSION); + + /* And send it off */ + sendto(lan_ad_client_fd, &ad_packet_buffer, 2*sizeof(uint32_t), 0, + addr->ai_addr, addr->ai_addrlen); + break; + } + + case RARCH_NETPLAY_DISCOVERY_CTL_LAN_GET_RESPONSES: + return netplay_lan_ad_client(); + + case RARCH_NETPLAY_DISCOVERY_CTL_LAN_CLEAR_RESPONSES: + discovered_hosts.size = 0; + break; + + default: + return false; + } + + return true; +} + +static bool init_lan_ad_server_socket(netplay_t *netplay, uint16_t port) +{ + struct addrinfo *addr = NULL; + int fd = socket_init((void **) &addr, port, NULL, SOCKET_TYPE_DATAGRAM); + + if (fd < 0) + goto error; + + if (!socket_bind(fd, (void*)addr)) + { + socket_close(fd); + goto error; + } + + lan_ad_server_fd = fd; + freeaddrinfo_retro(addr); + + return true; + +error: + if (addr) + freeaddrinfo_retro(addr); + RARCH_ERR("Failed to initialize netplay advertisement socket.\n"); + return false; +} + +bool netplay_lan_ad_server(netplay_t *netplay) +{ + fd_set fds; + struct timeval tmp_tv = {0}; + struct sockaddr their_addr; + socklen_t addr_size; + rarch_system_info_t *info = NULL; + size_t bufloc; + + if (lan_ad_server_fd < 0 && !init_lan_ad_server_socket(netplay, RARCH_DEFAULT_PORT)) + return false; + + /* Check for any ad queries */ + while (1) + { + FD_ZERO(&fds); + FD_SET(lan_ad_server_fd, &fds); + if (socket_select(lan_ad_server_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) + break; + if (!FD_ISSET(lan_ad_server_fd, &fds)) + break; + + /* Somebody queried, so check that it's valid */ + addr_size = sizeof(their_addr); + if (recvfrom(lan_ad_server_fd, (char*)&ad_packet_buffer, + sizeof(struct ad_packet), 0, &their_addr, &addr_size) >= + (ssize_t) (2*sizeof(uint32_t))) + { + /* Make sure it's a valid query */ + if (memcmp((void *) &ad_packet_buffer, "RANQ", 4)) + continue; + + /* For this version */ + if (ntohl(ad_packet_buffer.protocol_version) != + NETPLAY_PROTOCOL_VERSION) + continue; + + runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); + + /* Now build our response */ + memset(&ad_packet_buffer, 0, sizeof(struct ad_packet)); + memcpy(&ad_packet_buffer, "RANS", 4); + ad_packet_buffer.protocol_version = + htonl(NETPLAY_PROTOCOL_VERSION); + ad_packet_buffer.port = htonl(netplay->tcp_port); + strncpy(ad_packet_buffer.retroarch_version, PACKAGE_VERSION, + NETPLAY_HOST_STR_LEN); + strncpy(ad_packet_buffer.nick, netplay->nick, NETPLAY_HOST_STR_LEN); + if (info) + { + strncpy(ad_packet_buffer.core, info->info.library_name, + NETPLAY_HOST_STR_LEN); + strncpy(ad_packet_buffer.core_version, info->info.library_version, + NETPLAY_HOST_STR_LEN); + } + + /* And send it */ + sendto(lan_ad_server_fd, (const char*)&ad_packet_buffer, + sizeof(struct ad_packet), 0, &their_addr, addr_size); + } + } + + return true; +} + +bool netplay_lan_ad_client(void) +{ + fd_set fds; + struct timeval tmp_tv = {0}; + struct sockaddr their_addr; + socklen_t addr_size; + rarch_system_info_t *info = NULL; + size_t bufloc; + + if (lan_ad_client_fd < 0) + return false; + + /* Check for any ad queries */ + while (1) + { + FD_ZERO(&fds); + FD_SET(lan_ad_client_fd, &fds); + if (socket_select(lan_ad_client_fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) + break; + if (!FD_ISSET(lan_ad_client_fd, &fds)) + break; + + /* Somebody queried, so check that it's valid */ + addr_size = sizeof(their_addr); + if (recvfrom(lan_ad_client_fd, (char*)&ad_packet_buffer, + sizeof(struct ad_packet), 0, &their_addr, &addr_size) >= + sizeof(struct ad_packet)) + { + struct netplay_host *host; + + /* Make sure it's a valid response */ + if (memcmp((void *) &ad_packet_buffer, "RANS", 4)) + continue; + + /* For this version */ + if (ntohl(ad_packet_buffer.protocol_version) != NETPLAY_PROTOCOL_VERSION) + continue; + + /* Allocate space for it */ + if (discovered_hosts.size >= discovered_hosts_allocated) + { + size_t allocated = discovered_hosts_allocated; + struct netplay_host *new_hosts; + + if (allocated == 0) allocated = 2; + else allocated *= 2; + + if (discovered_hosts.hosts) + new_hosts = realloc(discovered_hosts.hosts, allocated * sizeof(struct netplay_host)); + else + /* Should be equivalent to realloc, but I don't trust screwy libcs */ + new_hosts = malloc(allocated * sizeof(struct netplay_host)); + + if (!new_hosts) + return false; + + discovered_hosts.hosts = new_hosts; + discovered_hosts_allocated = allocated; + } + + /* Get our host structure */ + host = &discovered_hosts.hosts[discovered_hosts.size++]; + + /* Copy in the response */ + memset(host, 0, sizeof(struct netplay_host)); + host->addr = their_addr; + host->addrlen = addr_size; + strncpy(host->nick, ad_packet_buffer.nick, NETPLAY_HOST_STR_LEN); + strncpy(host->core, ad_packet_buffer.core, NETPLAY_HOST_STR_LEN); + strncpy(host->core_version, ad_packet_buffer.core_version, + NETPLAY_HOST_STR_LEN); + strncpy(host->content, ad_packet_buffer.content, + NETPLAY_HOST_STR_LEN); + host->nick[NETPLAY_HOST_STR_LEN-1] = + host->core[NETPLAY_HOST_STR_LEN-1] = + host->core_version[NETPLAY_HOST_STR_LEN-1] = + host->content[NETPLAY_HOST_STR_LEN-1] = '\0'; + } + } + + return true; +} diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index b24c8cccdf..3b72e52160 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -258,6 +258,8 @@ bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta); bool netplay_cmd_request_savestate(netplay_t *netplay); -bool netplay_ad_server(netplay_t *netplay, int ad_fd); +/* DISCOVERY: */ + +bool netplay_lan_ad_server(netplay_t *netplay); #endif