laforge has submitted this change. ( 
https://gerrit.osmocom.org/c/libosmocore/+/19140 )

Change subject: osmo_sock_init2: improve support for AF_UNSPEC
......................................................................

osmo_sock_init2: improve support for AF_UNSPEC

osmo_sock_init2 abstract two calls of getaddrinfo into one.
While there aren't problems with AF_INET or AF_INET6. When using
AF_UNSPEC there are corner cases when this fails. E.g. calling
local_host with "" and remote_host with an IPv6 only address results in
setting up a local socket with AF_INET while trying to connect from there 
towards
AF_INET6 will most likely fail.
To prevent such cases with AF_UNSPEC, search prio calling any syscalls if local 
and remote site
supports AF_INET or AF_INET6. In case both supported, prefer AF_INET6

Change-Id: I397c633931fd00d4f083955a3c49a40fb002d766
---
M src/socket.c
M tests/socket/socket_test.c
M tests/socket/socket_test.err
M tests/socket/socket_test.ok
4 files changed, 105 insertions(+), 14 deletions(-)

Approvals:
  laforge: Looks good to me, approved
  fixeria: Looks good to me, but someone else must approve
  Jenkins Builder: Verified



diff --git a/src/socket.c b/src/socket.c
index 7fa9ab3..9c60821 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -260,22 +260,85 @@
                   const char *local_host, uint16_t local_port,
                   const char *remote_host, uint16_t remote_port, unsigned int 
flags)
 {
-       struct addrinfo *result, *rp;
+       struct addrinfo *local = NULL, *remote = NULL, *rp;
        int sfd = -1, rc, on = 1;

+       bool local_ipv4 = false, local_ipv6 = false;
+       bool remote_ipv4 = false, remote_ipv6 = false;
+
        if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) {
                LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either 
"
                        "BIND or CONNECT flags\n");
                return -EINVAL;
        }

+       /* figure out local address infos */
+       if (flags & OSMO_SOCK_F_BIND) {
+               local = addrinfo_helper(family, type, proto, local_host, 
local_port, true);
+               if (!local)
+                       return -EINVAL;
+       }
+
+       /* figure out remote address infos */
+       if (flags & OSMO_SOCK_F_CONNECT) {
+               remote = addrinfo_helper(family, type, proto, remote_host, 
remote_port, false);
+               if (!remote) {
+                       if (local)
+                               freeaddrinfo(local);
+
+                       return -EINVAL;
+               }
+       }
+
+       /* It must do a full run to ensure AF_UNSPEC does not fail.
+        * In case first local valid entry is IPv4 and only remote valid entry
+        * is IPv6 or vice versa */
+       if (family == AF_UNSPEC) {
+               for (rp = local; rp != NULL; rp = rp->ai_next) {
+                       switch (rp->ai_family) {
+                               case AF_INET:
+                                       local_ipv4 = true;
+                                       break;
+                               case AF_INET6:
+                                       local_ipv6 = true;
+                                       break;
+                       }
+               }
+
+               for (rp = remote; rp != NULL; rp = rp->ai_next) {
+                       switch (rp->ai_family) {
+                               case AF_INET:
+                                       remote_ipv4 = true;
+                                       break;
+                               case AF_INET6:
+                                       remote_ipv6 = true;
+                                       break;
+                       }
+               }
+
+               /* priotize ipv6 as per RFC */
+               if (local_ipv6 && remote_ipv6)
+                       family = AF_INET6;
+               else if (local_ipv4 && remote_ipv4)
+                       family = AF_INET;
+               else {
+                       if (local)
+                               freeaddrinfo(local);
+                       if (remote)
+                               freeaddrinfo(remote);
+                       LOGP(DLGLOBAL, LOGL_ERROR, "Unable to find a common 
protocol (IPv4 or IPv6) for local host: %s and remote host: %s.\n",
+                            local_host, remote_host);
+                       return -ENODEV;
+               }
+       }
+
        /* figure out local side of socket */
        if (flags & OSMO_SOCK_F_BIND) {
-               result = addrinfo_helper(family, type, proto, local_host, 
local_port, true);
-               if (!result)
-                       return -EINVAL;
+               for (rp = local; rp != NULL; rp = rp->ai_next) {
+                       /* When called with AF_UNSPEC, family will set to IPv4 
or IPv6 */
+                       if (rp->ai_family != family)
+                               continue;

-               for (rp = result; rp != NULL; rp = rp->ai_next) {
                        sfd = socket_helper(rp, flags);
                        if (sfd < 0)
                                continue;
@@ -302,8 +365,11 @@
                        }
                        break;
                }
-               freeaddrinfo(result);
+
+               freeaddrinfo(local);
                if (rp == NULL) {
+                       if (remote)
+                               freeaddrinfo(remote);
                        LOGP(DLGLOBAL, LOGL_ERROR, "no suitable local addr 
found for: %s:%u\n",
                                local_host, local_port);
                        return -ENODEV;
@@ -316,14 +382,11 @@

        /* figure out remote side of socket */
        if (flags & OSMO_SOCK_F_CONNECT) {
-               result = addrinfo_helper(family, type, proto, remote_host, 
remote_port, false);
-               if (!result) {
-                       if (sfd >= 0)
-                               close(sfd);
-                       return -EINVAL;
-               }
+               for (rp = remote; rp != NULL; rp = rp->ai_next) {
+                       /* When called with AF_UNSPEC, family will set to IPv4 
or IPv6 */
+                       if (rp->ai_family != family)
+                               continue;

-               for (rp = result; rp != NULL; rp = rp->ai_next) {
                        if (sfd < 0) {
                                sfd = socket_helper(rp, flags);
                                if (sfd < 0)
@@ -343,7 +406,8 @@
                        }
                        break;
                }
-               freeaddrinfo(result);
+
+               freeaddrinfo(remote);
                if (rp == NULL) {
                        LOGP(DLGLOBAL, LOGL_ERROR, "no suitable remote addr 
found for: %s:%u\n",
                                remote_host, remote_port);
diff --git a/tests/socket/socket_test.c b/tests/socket/socket_test.c
index 37e0281..e68243c 100644
--- a/tests/socket/socket_test.c
+++ b/tests/socket/socket_test.c
@@ -120,6 +120,27 @@
         * FreeBSD 10 or 11 VM at home */
        OSMO_ASSERT(!strncmp(name, "(r=127.0.0.1:53<->l=127.0.0.1", 29));
 #endif
+
+       printf("Checking osmo_sock_init2(AF_UNSPEC) must fail on mixed IPv4 & 
IPv6\n");
+       fd = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, "127.0.0.1", 
0, "::1", 53,
+                            OSMO_SOCK_F_BIND|OSMO_SOCK_F_CONNECT);
+       OSMO_ASSERT(fd < 0);
+
+       printf("Checking osmo_sock_init2(AF_UNSPEC) must fail on mixed IPv6 & 
IPv4\n");
+       fd = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, "::1", 0, 
"127.0.0.1", 53,
+                            OSMO_SOCK_F_BIND|OSMO_SOCK_F_CONNECT);
+       OSMO_ASSERT(fd < 0);
+
+       printf("Checking osmo_sock_init2(AF_UNSPEC) BIND + CONNECT on IPv4\n");
+       fd = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, "127.0.0.1", 
0, "127.0.0.1", 53,
+                            OSMO_SOCK_F_BIND|OSMO_SOCK_F_CONNECT);
+       OSMO_ASSERT(fd >= 0);
+
+       printf("Checking osmo_sock_init2(AF_UNSPEC) BIND + CONNECT on IPv6\n");
+       fd = osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, "::1", 0, 
"::1", 53,
+                            OSMO_SOCK_F_BIND|OSMO_SOCK_F_CONNECT);
+       OSMO_ASSERT(fd >= 0);
+
        talloc_free(name);

        return 0;
diff --git a/tests/socket/socket_test.err b/tests/socket/socket_test.err
index ed6e186..0f0f8da 100644
--- a/tests/socket/socket_test.err
+++ b/tests/socket/socket_test.err
@@ -1,2 +1,4 @@
 invalid: both bind and connect flags set: 0.0.0.0:0
 invalid: you have to specify either BIND or CONNECT flags
+Unable to find a common protocol (IPv4 or IPv6) for local host: 127.0.0.1 and 
remote host: ::1.
+Unable to find a common protocol (IPv4 or IPv6) for local host: ::1 and remote 
host: 127.0.0.1.
diff --git a/tests/socket/socket_test.ok b/tests/socket/socket_test.ok
index 4b24fbc..4265be8 100644
--- a/tests/socket/socket_test.ok
+++ b/tests/socket/socket_test.ok
@@ -5,3 +5,7 @@
 Checking osmo_sock_init2() for OSMO_SOCK_F_NONBLOCK
 Checking osmo_sock_init2() for invalid flags
 Checking osmo_sock_init2() for combined BIND + CONNECT
+Checking osmo_sock_init2(AF_UNSPEC) must fail on mixed IPv4 & IPv6
+Checking osmo_sock_init2(AF_UNSPEC) must fail on mixed IPv6 & IPv4
+Checking osmo_sock_init2(AF_UNSPEC) BIND + CONNECT on IPv4
+Checking osmo_sock_init2(AF_UNSPEC) BIND + CONNECT on IPv6

--
To view, visit https://gerrit.osmocom.org/c/libosmocore/+/19140
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: libosmocore
Gerrit-Branch: master
Gerrit-Change-Id: I397c633931fd00d4f083955a3c49a40fb002d766
Gerrit-Change-Number: 19140
Gerrit-PatchSet: 9
Gerrit-Owner: lynxis lazus <lyn...@fe80.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanits...@sysmocom.de>
Gerrit-Reviewer: laforge <lafo...@osmocom.org>
Gerrit-Reviewer: lynxis lazus <lyn...@fe80.eu>
Gerrit-Reviewer: pespin <pes...@sysmocom.de>
Gerrit-MessageType: merged

Reply via email to