refactor server discovery into its own class

This commit is contained in:
Tim Felgentreff 2020-12-21 20:40:08 +01:00
parent 56dd0f5cb7
commit f4b6f16e54
4 changed files with 202 additions and 110 deletions

View file

@ -236,6 +236,7 @@ set(network_SRCS
src/network/network.cpp
src/network/netsockets.cpp
src/network/online_service.cpp
src/network/mdns.cpp
)
source_group(network FILES ${network_SRCS})

48
src/include/mdns.h Normal file
View file

@ -0,0 +1,48 @@
// _________ __ __
// / _____// |_____________ _/ |______ ____ __ __ ______
// \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/
// / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ |
// /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ >
// \/ \/ \//_____/ \/
// ______________________ ______________________
// T H E W A R B E G I N S
// Stratagus - A free fantasy real time strategy game engine
//
/**@name mdns.h - LAN server discovery. */
//
// (c) Copyright 2020 by Tim Felgentreff
//
// This package 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 Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
// 02111-1307, USA.
//
#include <functional>
#include "network/mdns.h"
class MDNS {
public:
MDNS() : serviceSocket(-1), queryId(-1), numSockets(-1) { };
~MDNS();
void AnswerServerQueries();
void QueryServers(std::function<void(char*)> callback);
private:
int serviceSocket;
int queryId;
int querySockets[20];
int numSockets;
};

141
src/network/mdns.cpp Normal file
View file

@ -0,0 +1,141 @@
// _________ __ __
// / _____// |_____________ _/ |______ ____ __ __ ______
// \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/
// / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ |
// /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ >
// \/ \/ \//_____/ \/
// ______________________ ______________________
// T H E W A R B E G I N S
// Stratagus - A free fantasy real time strategy game engine
//
/**@name mdns.cpp - LAN server discovery. */
//
// (c) Copyright 2020 by Tim Felgentreff
//
// This package 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 Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
// 02111-1307, USA.
//
#include "mdns.h"
#include "game.h"
#include "net_lowlevel.h"
#include "network.h"
static int service_callback(int sock, const struct sockaddr* from, size_t addrlen, mdns_entry_type_t entry,
uint16_t query_id, uint16_t rtype, uint16_t rclass, uint32_t ttl, const void* data,
size_t size, size_t name_offset, size_t name_length, size_t record_offset,
size_t record_length, void* user_data) {
if (rtype == MDNS_RECORDTYPE_PTR && entry == MDNS_ENTRYTYPE_QUESTION) {
char namebuffer[256];
mdns_string_t service = mdns_record_parse_ptr(data, size, record_offset, record_length, namebuffer, sizeof(namebuffer));
std::string offeredService = GameName;
if (strncmp(offeredService.c_str(), service.str, offeredService.size()) == 0) {
uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
std::string hostname = NetGetHostname();
if (!unicast) {
addrlen = 0;
}
char buffer[256];
unsigned long ips[20];
int numIps = NetSocketAddr(ips, 20);
for (int i = 0; i < numIps; i++) {
mdns_query_answer(sock, from, addrlen, buffer, sizeof(buffer), query_id,
offeredService.c_str(), offeredService.size(),
hostname.c_str(), hostname.size(),
ips[i], NULL, CNetworkParameter::Instance.localPort,
NULL, 0);
}
}
}
return 0;
}
void MDNS::AnswerServerQueries() {
if (serviceSocket == -1) {
// When recieving, a socket can recieve data from all network interfaces
struct sockaddr_in sock_addr;
memset(&sock_addr, 0, sizeof(struct sockaddr_in));
sock_addr.sin_family = AF_INET;
#ifdef _WIN32
sock_addr.sin_addr = in4addr_any;
#else
sock_addr.sin_addr.s_addr = INADDR_ANY;
#endif
sock_addr.sin_port = htons(MDNS_PORT);
#ifdef __APPLE__
sock_addr.sin_len = sizeof(struct sockaddr_in);
#endif
serviceSocket = mdns_socket_open_ipv4(&sock_addr);
}
char buffer[1024];
mdns_socket_listen(serviceSocket, buffer, sizeof(buffer), service_callback, NULL);
}
static int query_callback(int sock, const struct sockaddr* from, size_t addrlen, mdns_entry_type_t entry,
uint16_t query_id, uint16_t rtype, uint16_t rclass, uint32_t ttl, const void* data,
size_t size, size_t name_offset, size_t name_length, size_t record_offset,
size_t record_length, void* user_data) {
std::function<void(char*)> *cb = static_cast<std::function<void(char*)>*>(user_data);
if (rtype == MDNS_RECORDTYPE_A && entry == MDNS_ENTRYTYPE_ADDITIONAL) {
struct sockaddr_in addr;
mdns_record_parse_a(data, size, record_offset, record_length, &addr);
char buf[24] = {'\0'};
sprintf(buf, "%d.%d.%d.%d", NIPQUAD(ntohl(addr.sin_addr.s_addr)));
(*cb)(buf);
}
return 0;
}
void MDNS::QueryServers(std::function<void(char*)> callback) {
char buffer[1024];
if (numSockets == -1) {
// When sending, each socket can only send to one network interface
// Thus we need to open one socket for each interface
unsigned long ips[20];
numSockets = NetSocketAddr(ips, 20);
struct sockaddr_in sock_addr;
for (int i = 0; i < numSockets; i++) {
memset(&sock_addr, 0, sizeof(struct sockaddr_in));
sock_addr.sin_family = AF_INET;
sock_addr.sin_addr.s_addr = ips[i];
#ifdef __APPLE__
sock_addr.sin_len = sizeof(struct sockaddr_in);
#endif
querySockets[i] = mdns_socket_open_ipv4(&sock_addr);
}
}
for (int i = 0; i < numSockets; i++) {
queryId = mdns_query_send(querySockets[i], MDNS_RECORDTYPE_PTR,
GameName.c_str(), GameName.size(),
buffer, sizeof(buffer), 0);
int responses = mdns_query_recv(querySockets[i], buffer,
sizeof(buffer), query_callback,
&callback, queryId);
}
}
MDNS::~MDNS() {
if (serviceSocket != -1) {
mdns_socket_close(serviceSocket);
serviceSocket = -1;
}
for (int i = 0; i < numSockets; i++) {
mdns_socket_close(querySockets[i]);
numSockets = -1;
queryId = -1;
}
}

View file

@ -48,9 +48,7 @@
#include "settings.h"
#include "version.h"
#include "video.h"
#include "net_lowlevel.h"
#include "network/mdns.h"
#include "mdns.h"
//----------------------------------------------------------------------------
// Declaration
@ -96,6 +94,8 @@ int NoRandomPlacementMultiplayer = 0; /// Disable the random placement of player
CServerSetup ServerSetupState; // Server selection state for Multiplayer clients
CServerSetup LocalSetupState; // Local selection state for Multiplayer clients
MDNS MdnsService;
class CServer
{
public:
@ -1442,72 +1442,17 @@ int FindHostIndexBy(const CHost &host)
return -1;
}
static int service_callback(int sock, const struct sockaddr* from, size_t addrlen, mdns_entry_type_t entry,
uint16_t query_id, uint16_t rtype, uint16_t rclass, uint32_t ttl, const void* data,
size_t size, size_t name_offset, size_t name_length, size_t record_offset,
size_t record_length, void* user_data) {
if (rtype == MDNS_RECORDTYPE_PTR && entry == MDNS_ENTRYTYPE_QUESTION) {
char namebuffer[256];
mdns_string_t service = mdns_record_parse_ptr(data, size, record_offset, record_length, namebuffer, sizeof(namebuffer));
std::string offeredService = GameName;
if (strncmp(offeredService.c_str(), service.str, offeredService.size()) == 0) {
uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
std::string hostname = NetGetHostname();
if (!unicast) {
addrlen = 0;
}
char buffer[256];
unsigned long ips[20];
int numIps = NetSocketAddr(ips, 20);
for (int i = 0; i < numIps; i++) {
mdns_query_answer(sock, from, addrlen, buffer, sizeof(buffer), query_id,
offeredService.c_str(), offeredService.size(),
hostname.c_str(), hostname.size(),
ips[i], NULL, CNetworkParameter::Instance.localPort,
NULL, 0);
}
}
}
return 0;
}
/**
** Server Menu Loop: Send out server request messages
*/
void NetworkProcessServerRequest()
{
static int mDNS_socket = -1;
if (GameRunning) {
if (mDNS_socket != -1) {
mdns_socket_close(mDNS_socket);
mDNS_socket = -1;
}
return;
// Game already started...
}
if (mDNS_socket == -1) {
// When recieving, a socket can recieve data from all network interfaces
struct sockaddr_in sock_addr;
memset(&sock_addr, 0, sizeof(struct sockaddr_in));
sock_addr.sin_family = AF_INET;
#ifdef _WIN32
sock_addr.sin_addr = in4addr_any;
#else
sock_addr.sin_addr.s_addr = INADDR_ANY;
#endif
sock_addr.sin_port = htons(MDNS_PORT);
#ifdef __APPLE__
sock_addr.sin_len = sizeof(struct sockaddr_in);
#endif
mDNS_socket = mdns_socket_open_ipv4(&sock_addr);
} else {
size_t cap = 2048;
void *buffer = malloc(cap);
mdns_socket_listen(mDNS_socket, buffer, cap, service_callback, NULL);
free(buffer);
}
MdnsService.AnswerServerQueries();
Server.Update(FrameCounter);
}
@ -1928,64 +1873,21 @@ static int CclNoRandomPlacementMultiplayer(lua_State *l)
return 0;
}
static int query_callback(int sock, const struct sockaddr* from, size_t addrlen, mdns_entry_type_t entry,
uint16_t query_id, uint16_t rtype, uint16_t rclass, uint32_t ttl, const void* data,
size_t size, size_t name_offset, size_t name_length, size_t record_offset,
size_t record_length, void* user_data) {
lua_State *l = (lua_State *)user_data;
if (rtype == MDNS_RECORDTYPE_A && entry == MDNS_ENTRYTYPE_ADDITIONAL) {
struct sockaddr_in addr;
mdns_record_parse_a(data, size, record_offset, record_length, &addr);
char buf[24] = {'\0'};
sprintf(buf, "%d.%d.%d.%d", NIPQUAD(ntohl(addr.sin_addr.s_addr)));
int i = lua_objlen(l, -1) + 1;
lua_pushnumber(l, i);
lua_pushstring(l, buf);
lua_settable(l, -3);
}
return 0;
}
static int CclNetworkDiscoverServers(lua_State *l)
{
static int mDNS_queryId = -1;
static int mDNS_querySocket[20] = {-1};
static int numSockets = -1;
LuaCheckArgs(l, 1);
bool start = LuaToBoolean(l, 1);
auto callback= [l](char* ip) {
auto i = lua_objlen(l, -1) + 1;
lua_pushnumber(l, i);
lua_pushstring(l, ip);
lua_settable(l, -3);
};
lua_newtable(l);
if (start) {
char buffer[1024];
if (numSockets == -1) {
// When sending, each socket can only send to one network interface
// Thus we need to open one socket for each interface
unsigned long ips[20];
numSockets = NetSocketAddr(ips, 20);
struct sockaddr_in sock_addr;
for (int i = 0; i < numSockets; i++) {
memset(&sock_addr, 0, sizeof(struct sockaddr_in));
sock_addr.sin_family = AF_INET;
sock_addr.sin_addr.s_addr = ips[i];
#ifdef __APPLE__
sock_addr.sin_len = sizeof(struct sockaddr_in);
#endif
mDNS_querySocket[i] = mdns_socket_open_ipv4(&sock_addr);
}
}
for (int i = 0; i < numSockets; i++) {
mDNS_queryId = mdns_query_send(mDNS_querySocket[i], MDNS_RECORDTYPE_PTR,
GameName.c_str(), GameName.size(),
buffer, sizeof(buffer), 0);
int responses = mdns_query_recv(mDNS_querySocket[i], buffer, sizeof(buffer), query_callback, l, mDNS_queryId);
}
} else {
for (int i = 0; i < numSockets; i++) {
mdns_socket_close(mDNS_querySocket[i]);
numSockets = -1;
mDNS_queryId = -1;
}
MdnsService.QueryServers(callback);
}
return 1;