(Netplay/UPnP) Smart interface selection (#13470)

Find the most suitable address for UPnP by scoring interfaces on how close their address is to the device's address.
This commit is contained in:
Cthulhu-throwaway 2022-01-10 11:52:15 -03:00 committed by GitHub
parent 3e3cf904ca
commit fb2d600837
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 125 additions and 80 deletions

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010-2021 The RetroArch team /* Copyright (C) 2010-2022 The RetroArch team
* *
* --------------------------------------------------------------------------------------- * ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (net_natt.h). * The following license statement only applies to this file (net_natt.h).
@ -54,10 +54,11 @@ enum nat_traversal_status
struct natt_device struct natt_device
{ {
struct sockaddr_in addr;
struct sockaddr_in ext_addr; struct sockaddr_in ext_addr;
char desc [512]; char desc [256];
char control [512]; char control [256];
char service_type[512]; char service_type[256];
bool busy; bool busy;
}; };
@ -69,10 +70,10 @@ struct natt_request
bool success; bool success;
}; };
/* Use this struct to implement a higher-level interface. */
struct nat_traversal_data struct nat_traversal_data
{ {
struct natt_request request; struct natt_request request;
size_t iface;
enum natt_forward_type forward_type; enum natt_forward_type forward_type;
enum nat_traversal_status status; enum nat_traversal_status status;
}; };

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2021 The RetroArch team /* Copyright (C) 2016-2022 The RetroArch team
* *
* --------------------------------------------------------------------------------------- * ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (net_natt.c). * The following license statement only applies to this file (net_natt.c).
@ -87,7 +87,7 @@ bool natt_init(void)
if (!st->interfaces.size) if (!st->interfaces.size)
goto failure; goto failure;
st->fd = socket_init((void**) &bind_addr, 0, NULL, SOCKET_TYPE_DATAGRAM); st->fd = socket_init((void **) &bind_addr, 0, NULL, SOCKET_TYPE_DATAGRAM);
if (st->fd < 0) if (st->fd < 0)
goto failure; goto failure;
if (!bind_addr) if (!bind_addr)
@ -196,6 +196,7 @@ void natt_deinit(void)
*st->device.desc = '\0'; *st->device.desc = '\0';
*st->device.control = '\0'; *st->device.control = '\0';
*st->device.service_type = '\0'; *st->device.service_type = '\0';
memset(&st->device.addr, 0, sizeof(st->device.addr));
memset(&st->device.ext_addr, 0, sizeof(st->device.ext_addr)); memset(&st->device.ext_addr, 0, sizeof(st->device.ext_addr));
st->device.busy = false; st->device.busy = false;
#endif #endif
@ -215,13 +216,13 @@ bool natt_device_next(struct natt_device *device)
{ {
#ifndef HAVE_SOCKET_LEGACY #ifndef HAVE_SOCKET_LEGACY
fd_set fds; fd_set fds;
bool error;
char buf[2048]; char buf[2048];
ssize_t recvd; ssize_t recvd;
char *data; char *data;
size_t remaining; size_t remaining;
struct timeval tv = {0}; struct timeval tv = {0};
natt_state_t *st = &natt_st; socklen_t addr_size = sizeof(device->addr);
natt_state_t *st = &natt_st;
if (!device) if (!device)
return false; return false;
@ -233,6 +234,7 @@ bool natt_device_next(struct natt_device *device)
*device->desc = '\0'; *device->desc = '\0';
*device->control = '\0'; *device->control = '\0';
*device->service_type = '\0'; *device->service_type = '\0';
memset(&device->addr, 0, sizeof(device->addr));
memset(&device->ext_addr, 0, sizeof(device->ext_addr)); memset(&device->ext_addr, 0, sizeof(device->ext_addr));
device->busy = false; device->busy = false;
@ -245,8 +247,8 @@ bool natt_device_next(struct natt_device *device)
if (!FD_ISSET(st->fd, &fds)) if (!FD_ISSET(st->fd, &fds))
return cpu_features_get_time_usec() < st->timeout; return cpu_features_get_time_usec() < st->timeout;
recvd = socket_receive_all_nonblocking(st->fd, &error, recvd = recvfrom(st->fd, buf, sizeof(buf), 0,
buf, sizeof(buf)); (struct sockaddr *) &device->addr, &addr_size);
if (recvd <= 0) if (recvd <= 0)
return false; return false;
@ -262,19 +264,20 @@ bool natt_device_next(struct natt_device *device)
*lnbreak++ = '\0'; *lnbreak++ = '\0';
/* This also gets rid of any trailing carriage return. */ /* This also gets rid of any trailing carriage return. */
if (!strncasecmp(string_trim_whitespace(data), "Location:", string_trim_whitespace(data);
STRLEN_CONST("Location:")))
if (string_starts_with_case_insensitive(data, "Location:"))
{ {
char *location = string_trim_whitespace( char *location = string_trim_whitespace_left(
data + STRLEN_CONST("Location:")); data + STRLEN_CONST("Location:"));
if (!string_is_empty(location) && if (string_starts_with_case_insensitive(location, "http://"))
string_starts_with_case_insensitive(location, "http://"))
{ {
strlcpy(device->desc, location, /* Make sure the description URL isn't too long. */
sizeof(device->desc)); if (strlcpy(device->desc, location, sizeof(device->desc)) <
sizeof(device->desc))
return true; return true;
*device->desc = '\0';
} }
} }
@ -312,8 +315,13 @@ static bool build_control_url(rxml_node_t *control_url,
/* Do we already have the full url? */ /* Do we already have the full url? */
if (string_starts_with_case_insensitive(control_url->data, "http://")) if (string_starts_with_case_insensitive(control_url->data, "http://"))
{ {
strlcpy(device->control, control_url->data, /* Make sure the control URL isn't too long. */
sizeof(device->control)); if (strlcpy(device->control, control_url->data,
sizeof(device->control)) >= sizeof(device->control))
{
*device->control = '\0';
return false;
}
} }
else else
{ {
@ -324,15 +332,20 @@ static bool build_control_url(rxml_node_t *control_url,
strlcpy(device->control, device->desc, strlcpy(device->control, device->desc,
sizeof(device->control)); sizeof(device->control));
control_path = (char*) strchr(device->control + STRLEN_CONST("http://"), control_path = (char *) strchr(device->control +
'/'); STRLEN_CONST("http://"), '/');
if (control_path) if (control_path)
*control_path = '\0'; *control_path = '\0';
if (control_url->data[0] != '/') if (control_url->data[0] != '/')
strlcat(device->control, "/", sizeof(device->control)); strlcat(device->control, "/", sizeof(device->control));
strlcat(device->control, control_url->data, /* Make sure the control URL isn't too long. */
sizeof(device->control)); if (strlcat(device->control, control_url->data,
sizeof(device->control)) >= sizeof(device->control))
{
*device->control = '\0';
return false;
}
} }
return true; return true;

View File

@ -1,6 +1,6 @@
/* RetroArch - A frontend for libretro. /* RetroArch - A frontend for libretro.
* Copyright (C) 2017 - Gregor Richards * Copyright (C) 2017-2017 - Gregor Richards
* Copyright (C) 2021 - Roberto V. Rampim * Copyright (C) 2021-2022 - Roberto V. Rampim
* *
* RetroArch is free software: you can redistribute it and/or modify it under the terms * 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- * of the GNU General Public License as published by the Free Software Found-
@ -28,6 +28,82 @@
#include <net/net_natt.h> #include <net/net_natt.h>
#include "../network/netplay/netplay.h" #include "../network/netplay/netplay.h"
/* Find the most suitable address within the device's network. */
static bool find_local_address(struct net_ifinfo *interfaces,
struct natt_device *device, struct natt_request *request)
{
size_t i, j;
struct addrinfo **addrs = NULL;
uint32_t *scores = NULL;
uint32_t highest_score = 0;
struct addrinfo hints = {0};
uint8_t *dev_addr8 = (uint8_t *) &device->addr.sin_addr;
bool ret = false;
addrs = calloc(interfaces->size, sizeof(*addrs));
if (!addrs)
goto done;
scores = calloc(interfaces->size, sizeof(*scores));
if (!scores)
goto done;
hints.ai_family = AF_INET;
/* Score interfaces based on how close their address
is from the device's address. */
for (i = 0; i < interfaces->size; i++)
{
struct net_ifinfo_entry *entry = &interfaces->entries[i];
struct addrinfo **addr = &addrs[i];
uint32_t *score = &scores[i];
if (getaddrinfo_retro(entry->host, NULL, &hints, addr))
continue;
if (*addr)
{
uint8_t *addr8 = (uint8_t *)
&((struct sockaddr_in *) (*addr)->ai_addr)->sin_addr;
for (j = 0; j < sizeof(device->addr.sin_addr); j++)
{
if (addr8[j] != dev_addr8[j])
break;
(*score)++;
}
}
}
/* Get the highest scored interface. */
for (j = 0; j < interfaces->size; j++)
{
uint32_t score = scores[j];
if (score > highest_score)
{
highest_score = score;
i = j;
}
}
/* Skip a highest score of zero. */
if (highest_score)
{
/* Copy the interface's address to our request. */
memcpy(&request->addr.sin_addr,
&((struct sockaddr_in *) addrs[i]->ai_addr)->sin_addr,
sizeof(request->addr.sin_addr));
ret = true;
}
for (i = 0; i < interfaces->size; i++)
freeaddrinfo_retro(addrs[i]);
done:
free(addrs);
free(scores);
return ret;
}
static void task_netplay_nat_traversal_handler(retro_task_t *task) static void task_netplay_nat_traversal_handler(retro_task_t *task)
{ {
struct nat_traversal_data *data = task->task_data; struct nat_traversal_data *data = task->task_data;
@ -78,7 +154,6 @@ static void task_netplay_nat_traversal_handler(retro_task_t *task)
} }
if (natt_external_address(&natt_st->device, false)) if (natt_external_address(&natt_st->device, false))
{ {
data->iface = 0;
data->forward_type = NATT_FORWARD_TYPE_ANY; data->forward_type = NATT_FORWARD_TYPE_ANY;
data->status = NAT_TRAVERSAL_STATUS_OPEN; data->status = NAT_TRAVERSAL_STATUS_OPEN;
} }
@ -89,55 +164,18 @@ static void task_netplay_nat_traversal_handler(retro_task_t *task)
case NAT_TRAVERSAL_STATUS_OPEN: case NAT_TRAVERSAL_STATUS_OPEN:
{ {
size_t i;
struct addrinfo *addr = NULL;
struct net_ifinfo_entry *entry = NULL;
struct addrinfo hints = {0};
if (natt_st->device.ext_addr.sin_family != AF_INET) if (natt_st->device.ext_addr.sin_family != AF_INET)
{ {
data->status = NAT_TRAVERSAL_STATUS_SELECT_DEVICE; data->status = NAT_TRAVERSAL_STATUS_SELECT_DEVICE;
break; break;
} }
if (!find_local_address(&natt_st->interfaces,
/* Grab a suitable interface. */ &natt_st->device, &data->request))
hints.ai_family = AF_INET;
for (i = data->iface; i < natt_st->interfaces.size; i++)
{
struct net_ifinfo_entry *tmp_entry =
&natt_st->interfaces.entries[i];
if (getaddrinfo_retro(tmp_entry->host, NULL, &hints, &addr) ||
!addr)
continue;
/* Ignore non-LAN interfaces */
if (!netplay_is_lan_address(
(struct sockaddr_in *) addr->ai_addr))
{
freeaddrinfo_retro(addr);
addr = NULL;
continue;
}
entry = tmp_entry;
data->iface = i;
break;
}
/* No more interfaces? */
if (!entry)
{ {
data->status = NAT_TRAVERSAL_STATUS_SELECT_DEVICE; data->status = NAT_TRAVERSAL_STATUS_SELECT_DEVICE;
break; break;
} }
memcpy(&data->request.addr.sin_addr,
&((struct sockaddr_in *) addr->ai_addr)->sin_addr,
sizeof(data->request.addr.sin_addr));
freeaddrinfo_retro(addr);
if (natt_open_port(&natt_st->device, if (natt_open_port(&natt_st->device,
&data->request, data->forward_type, false)) &data->request, data->forward_type, false))
{ {
@ -145,12 +183,7 @@ static void task_netplay_nat_traversal_handler(retro_task_t *task)
break; break;
} }
if (data->forward_type == NATT_FORWARD_TYPE_ANY) if (data->forward_type == NATT_FORWARD_TYPE_ANY)
{
data->forward_type = NATT_FORWARD_TYPE_NONE; data->forward_type = NATT_FORWARD_TYPE_NONE;
break;
}
if (++data->iface < natt_st->interfaces.size)
data->forward_type = NATT_FORWARD_TYPE_ANY;
else else
data->status = NAT_TRAVERSAL_STATUS_SELECT_DEVICE; data->status = NAT_TRAVERSAL_STATUS_SELECT_DEVICE;
} }
@ -165,23 +198,19 @@ static void task_netplay_nat_traversal_handler(retro_task_t *task)
natt_interfaces_destroy(); natt_interfaces_destroy();
data->status = NAT_TRAVERSAL_STATUS_OPENED; data->status = NAT_TRAVERSAL_STATUS_OPENED;
goto finished; goto finished;
} }
if (data->forward_type == NATT_FORWARD_TYPE_ANY) if (data->forward_type == NATT_FORWARD_TYPE_ANY)
{ {
data->forward_type = NATT_FORWARD_TYPE_NONE; data->forward_type = NATT_FORWARD_TYPE_NONE;
data->status = NAT_TRAVERSAL_STATUS_OPEN; data->status = NAT_TRAVERSAL_STATUS_OPEN;
break;
}
if (++data->iface < natt_st->interfaces.size)
{
data->forward_type = NATT_FORWARD_TYPE_ANY;
data->status = NAT_TRAVERSAL_STATUS_OPEN;
} }
else else
data->status = NAT_TRAVERSAL_STATUS_SELECT_DEVICE; data->status = NAT_TRAVERSAL_STATUS_SELECT_DEVICE;
} }
break; break;
default: default:
break; break;
} }
@ -218,9 +247,11 @@ static void task_netplay_nat_close_handler(retro_task_t *task)
natt_deinit(); natt_deinit();
data->status = NAT_TRAVERSAL_STATUS_CLOSED; data->status = NAT_TRAVERSAL_STATUS_CLOSED;
goto finished; goto finished;
} }
break; break;
default: default:
break; break;
} }