From fb2d600837f9d4a2d6f91e29b8d7bd281a409dc6 Mon Sep 17 00:00:00 2001 From: Cthulhu-throwaway <96153783+Cthulhu-throwaway@users.noreply.github.com> Date: Mon, 10 Jan 2022 11:52:15 -0300 Subject: [PATCH] (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. --- libretro-common/include/net/net_natt.h | 11 +- libretro-common/net/net_natt.c | 57 ++++++---- tasks/task_netplay_nat_traversal.c | 137 +++++++++++++++---------- 3 files changed, 125 insertions(+), 80 deletions(-) diff --git a/libretro-common/include/net/net_natt.h b/libretro-common/include/net/net_natt.h index 43c7948681..3acba03dd7 100644 --- a/libretro-common/include/net/net_natt.h +++ b/libretro-common/include/net/net_natt.h @@ -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). @@ -54,10 +54,11 @@ enum nat_traversal_status struct natt_device { + struct sockaddr_in addr; struct sockaddr_in ext_addr; - char desc [512]; - char control [512]; - char service_type[512]; + char desc [256]; + char control [256]; + char service_type[256]; bool busy; }; @@ -69,10 +70,10 @@ struct natt_request bool success; }; +/* Use this struct to implement a higher-level interface. */ struct nat_traversal_data { struct natt_request request; - size_t iface; enum natt_forward_type forward_type; enum nat_traversal_status status; }; diff --git a/libretro-common/net/net_natt.c b/libretro-common/net/net_natt.c index 55505139ef..9bfbae5380 100644 --- a/libretro-common/net/net_natt.c +++ b/libretro-common/net/net_natt.c @@ -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). @@ -87,7 +87,7 @@ bool natt_init(void) if (!st->interfaces.size) 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) goto failure; if (!bind_addr) @@ -196,6 +196,7 @@ void natt_deinit(void) *st->device.desc = '\0'; *st->device.control = '\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)); st->device.busy = false; #endif @@ -215,13 +216,13 @@ bool natt_device_next(struct natt_device *device) { #ifndef HAVE_SOCKET_LEGACY fd_set fds; - bool error; char buf[2048]; ssize_t recvd; char *data; size_t remaining; - struct timeval tv = {0}; - natt_state_t *st = &natt_st; + struct timeval tv = {0}; + socklen_t addr_size = sizeof(device->addr); + natt_state_t *st = &natt_st; if (!device) return false; @@ -233,6 +234,7 @@ bool natt_device_next(struct natt_device *device) *device->desc = '\0'; *device->control = '\0'; *device->service_type = '\0'; + memset(&device->addr, 0, sizeof(device->addr)); memset(&device->ext_addr, 0, sizeof(device->ext_addr)); device->busy = false; @@ -245,8 +247,8 @@ bool natt_device_next(struct natt_device *device) if (!FD_ISSET(st->fd, &fds)) return cpu_features_get_time_usec() < st->timeout; - recvd = socket_receive_all_nonblocking(st->fd, &error, - buf, sizeof(buf)); + recvd = recvfrom(st->fd, buf, sizeof(buf), 0, + (struct sockaddr *) &device->addr, &addr_size); if (recvd <= 0) return false; @@ -262,19 +264,20 @@ bool natt_device_next(struct natt_device *device) *lnbreak++ = '\0'; /* This also gets rid of any trailing carriage return. */ - if (!strncasecmp(string_trim_whitespace(data), "Location:", - STRLEN_CONST("Location:"))) + string_trim_whitespace(data); + + if (string_starts_with_case_insensitive(data, "Location:")) { - char *location = string_trim_whitespace( + char *location = string_trim_whitespace_left( data + STRLEN_CONST("Location:")); - if (!string_is_empty(location) && - string_starts_with_case_insensitive(location, "http://")) + if (string_starts_with_case_insensitive(location, "http://")) { - strlcpy(device->desc, location, - sizeof(device->desc)); - - return true; + /* Make sure the description URL isn't too long. */ + if (strlcpy(device->desc, location, sizeof(device->desc)) < + sizeof(device->desc)) + 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? */ if (string_starts_with_case_insensitive(control_url->data, "http://")) { - strlcpy(device->control, control_url->data, - sizeof(device->control)); + /* Make sure the control URL isn't too long. */ + if (strlcpy(device->control, control_url->data, + sizeof(device->control)) >= sizeof(device->control)) + { + *device->control = '\0'; + return false; + } } else { @@ -324,15 +332,20 @@ static bool build_control_url(rxml_node_t *control_url, strlcpy(device->control, device->desc, sizeof(device->control)); - control_path = (char*) strchr(device->control + STRLEN_CONST("http://"), - '/'); + control_path = (char *) strchr(device->control + + STRLEN_CONST("http://"), '/'); if (control_path) *control_path = '\0'; if (control_url->data[0] != '/') strlcat(device->control, "/", sizeof(device->control)); - strlcat(device->control, control_url->data, - sizeof(device->control)); + /* Make sure the control URL isn't too long. */ + if (strlcat(device->control, control_url->data, + sizeof(device->control)) >= sizeof(device->control)) + { + *device->control = '\0'; + return false; + } } return true; diff --git a/tasks/task_netplay_nat_traversal.c b/tasks/task_netplay_nat_traversal.c index 7b074ce19e..c672965388 100644 --- a/tasks/task_netplay_nat_traversal.c +++ b/tasks/task_netplay_nat_traversal.c @@ -1,6 +1,6 @@ /* RetroArch - A frontend for libretro. - * Copyright (C) 2017 - Gregor Richards - * Copyright (C) 2021 - Roberto V. Rampim + * Copyright (C) 2017-2017 - Gregor Richards + * Copyright (C) 2021-2022 - Roberto V. Rampim * * 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- @@ -28,6 +28,82 @@ #include #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) { 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)) { - data->iface = 0; data->forward_type = NATT_FORWARD_TYPE_ANY; 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: { - 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) { data->status = NAT_TRAVERSAL_STATUS_SELECT_DEVICE; break; } - - /* Grab a suitable interface. */ - 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) + if (!find_local_address(&natt_st->interfaces, + &natt_st->device, &data->request)) { data->status = NAT_TRAVERSAL_STATUS_SELECT_DEVICE; 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, &data->request, data->forward_type, false)) { @@ -145,12 +183,7 @@ static void task_netplay_nat_traversal_handler(retro_task_t *task) break; } if (data->forward_type == NATT_FORWARD_TYPE_ANY) - { data->forward_type = NATT_FORWARD_TYPE_NONE; - break; - } - if (++data->iface < natt_st->interfaces.size) - data->forward_type = NATT_FORWARD_TYPE_ANY; else 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(); data->status = NAT_TRAVERSAL_STATUS_OPENED; + goto finished; } if (data->forward_type == NATT_FORWARD_TYPE_ANY) { data->forward_type = NATT_FORWARD_TYPE_NONE; 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 data->status = NAT_TRAVERSAL_STATUS_SELECT_DEVICE; } break; + default: break; } @@ -218,9 +247,11 @@ static void task_netplay_nat_close_handler(retro_task_t *task) natt_deinit(); data->status = NAT_TRAVERSAL_STATUS_CLOSED; + goto finished; } break; + default: break; }