summaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
authorGravatar Martin Szulecki2020-06-07 02:21:01 +0200
committerGravatar Nikias Bassen2020-06-07 11:34:14 +0200
commitee02dea462326879e9cf3293cd31172c25335be3 (patch)
tree5bfeee3b4f5b0735099b06cc2e4d731a11862783 /common
parent033202c9b1df142139358edec77709aa9ede4f16 (diff)
downloadlibusbmuxd-ee02dea462326879e9cf3293cd31172c25335be3.tar.gz
libusbmuxd-ee02dea462326879e9cf3293cd31172c25335be3.tar.bz2
socket: Fix socket_connect_addr() not connecting using IPv6 in some cases
This extends the socket helper with functions to determine the "scope" and a suitable "scope id" of an IPv6 address. While socket_connect_addr() prefers any initially supplied "scope id" to maintain routing information if possible, it will attempt to determine the best suitable route with the new helpers. This became a requirement during testing with remote usbmux connections that provide a different "scope id" and thus might cause IPv6 routing to not work at all. Thus the "scope id" is only valid per host.
Diffstat (limited to 'common')
-rw-r--r--common/socket.c125
1 files changed, 125 insertions, 0 deletions
diff --git a/common/socket.c b/common/socket.c
index 4d3956c..86cbf48 100644
--- a/common/socket.c
+++ b/common/socket.c
@@ -43,6 +43,10 @@ static int wsa_init = 0;
#include <netdb.h>
#include <arpa/inet.h>
#include <fcntl.h>
+#ifdef AF_INET6
+#include <net/if.h>
+#include <ifaddrs.h>
+#endif
#endif
#include "socket.h"
@@ -342,6 +346,115 @@ int socket_create(const char* addr, uint16_t port)
return sfd;
}
+#ifdef AF_INET6
+static uint32_t _in6_addr_scope(struct in6_addr* addr)
+{
+ uint32_t scope = 0;
+
+ if (IN6_IS_ADDR_MULTICAST(addr)) {
+ if (IN6_IS_ADDR_MC_NODELOCAL(addr)) {
+ scope = 1;
+ } else if (IN6_IS_ADDR_MC_LINKLOCAL(addr)) {
+ scope = 2;
+ } else if (IN6_IS_ADDR_MC_SITELOCAL(addr)) {
+ scope = 5;
+ }
+
+ return scope;
+ }
+
+ if (IN6_IS_ADDR_LINKLOCAL(addr)) {
+ scope = 2;
+ } else if (IN6_IS_ADDR_LOOPBACK(addr)) {
+ scope = 2;
+ } else if (IN6_IS_ADDR_SITELOCAL(addr)) {
+ scope = 5;
+ } else if (IN6_IS_ADDR_UNSPECIFIED(addr)) {
+ scope = 0;
+ }
+
+ return scope;
+}
+
+static int32_t _sockaddr_in6_scope_id(struct sockaddr_in6* addr)
+{
+ int32_t res = -1;
+ struct ifaddrs *ifaddr, *ifa;
+ uint32_t addr_scope;
+
+ /* get scope for requested address */
+ addr_scope = _in6_addr_scope(&addr->sin6_addr);
+ if (addr_scope == 0) {
+ /* global scope doesn't need a specific scope id */
+ return addr_scope;
+ }
+
+ /* get interfaces */
+ if (getifaddrs(&ifaddr) == -1) {
+ perror("getifaddrs");
+ return res;
+ }
+
+ /* loop over interfaces */
+ for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+ /* skip if no address is available */
+ if (ifa->ifa_addr == NULL) {
+ continue;
+ }
+
+ /* skip if wrong family */
+ if (ifa->ifa_addr->sa_family != AF_INET6) {
+ continue;
+ }
+
+ /* skip if not up */
+ if ((ifa->ifa_flags & IFF_UP) == 0) {
+ continue;
+ }
+
+ /* skip if not running */
+ if ((ifa->ifa_flags & IFF_RUNNING) == 0) {
+ continue;
+ }
+
+ struct sockaddr_in6* addr_in = (struct sockaddr_in6*)ifa->ifa_addr;
+
+ /* skip if scopes do not match */
+ if (_in6_addr_scope(&addr_in->sin6_addr) != addr_scope) {
+ continue;
+ }
+
+ /* use if address is equal */
+ if (memcmp(&addr->sin6_addr.s6_addr, &addr_in->sin6_addr.s6_addr, sizeof(addr_in->sin6_addr.s6_addr)) == 0) {
+ res = addr_in->sin6_scope_id;
+ /* if scope id equals the requested one then assume it was valid */
+ if (addr->sin6_scope_id == addr_in->sin6_scope_id) {
+ break;
+ } else {
+ continue;
+ }
+ }
+
+ /* skip loopback interface if not already matched exactly above */
+ if ((ifa->ifa_flags & IFF_LOOPBACK) != 0) {
+ continue;
+ }
+
+ /* set the scope id of this interface as most likely candidate */
+ res = addr_in->sin6_scope_id;
+
+ /* if scope id equals the requested one then assume it was valid */
+ if (addr->sin6_scope_id == addr_in->sin6_scope_id) {
+ break;
+ }
+ }
+
+ freeifaddrs(ifaddr);
+
+ return res;
+}
+#endif
+
int socket_connect_addr(struct sockaddr* addr, uint16_t port)
{
int sfd = -1;
@@ -369,6 +482,18 @@ int socket_connect_addr(struct sockaddr* addr, uint16_t port)
else if (addr->sa_family == AF_INET6) {
struct sockaddr_in6* addr_in = (struct sockaddr_in6*)addr;
addr_in->sin6_port = htons(port);
+
+ /*
+ * IPv6 Routing Magic:
+ *
+ * If the scope of the address is a link-local one, IPv6 requires the
+ * scope id set to an interface number to allow proper routing. However,
+ * as the provided sockaddr might contain a wrong scope id, we must find
+ * a scope id from a suitable interface on this system or routing might
+ * fail. An IPv6 guru should have another look though...
+ */
+ addr_in->sin6_scope_id = _sockaddr_in6_scope_id(addr_in);
+
addrlen = sizeof(struct sockaddr_in6);
}
#endif