pespin has submitted this change. ( 
https://gerrit.osmocom.org/c/libosmocore/+/35232?usp=email )

Change subject: socket: Reimplement osmo_sock_init2_multiaddr()
......................................................................

socket: Reimplement osmo_sock_init2_multiaddr()

This is an attempt to fix several downsides of current
osmo_sock_init2_multiaddr() API, mainly the requirement to pass an explicit
local address (!NULL). It also now works fine if OSMO_SOCK_F_BIND flag
is not used.

This reimplementation is based on the follwing logic:
- If caller passed family=AF_INET or family=AF_INET6, that same family
  is used and kernel will fail if something is wrong.
- If caller passes family=AF_UNSPEC, the function will try to find the
  required family to create the socket. The decision is taken on the
assumption that an AF_INET6 socket can handle both AF_INET6 and AF_INET
addresses (through v4v6 mapping). Hence, if any of the addresses in the
local or remote set of addresses resolves through getaddrinfo() to an
IPv6 address, then AF_INET6 is used; AF_INET is used otherwise.

Related: OS#6279
Change-Id: I2641fbaca6f477404b094dbc53c0c1a3dd3fd2fd
---
M src/core/socket.c
1 file changed, 128 insertions(+), 44 deletions(-)

Approvals:
  Jenkins Builder: Verified
  osmith: Looks good to me, approved




diff --git a/src/core/socket.c b/src/core/socket.c
index 1dc8e46..0bc275b 100644
--- a/src/core/socket.c
+++ b/src/core/socket.c
@@ -616,18 +616,45 @@

 #ifdef HAVE_LIBSCTP

-/* Check whether there's an IPv6 Addr as first option of any addrinfo item in 
the addrinfo set */
+/* Check whether there's an addrinfo item in the addrinfo set with an IPv4 or 
IPv6 option */
 static void addrinfo_has_v4v6addr(const struct addrinfo **result, size_t 
result_count, bool *has_v4, bool *has_v6)
 {
        size_t host_idx;
+       const struct addrinfo *rp;
        *has_v4 = false;
        *has_v6 = false;

        for (host_idx = 0; host_idx < result_count; host_idx++) {
-               if (result[host_idx]->ai_family == AF_INET)
-                       *has_v4 = true;
-               else if (result[host_idx]->ai_family == AF_INET6)
-                       *has_v6 = true;
+               for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
+                       if (result[host_idx]->ai_family == AF_INET)
+                               *has_v4 = true;
+                       else if (result[host_idx]->ai_family == AF_INET6)
+                               *has_v6 = true;
+               }
+       }
+}
+
+/* Check whether there's an addrinfo item in the addrinfo set with only an 
IPv4 or IPv6 option */
+static void addrinfo_has_v4v6only_addr(const struct addrinfo **result, size_t 
result_count, bool *has_v4only, bool *has_v6only)
+{
+       size_t host_idx;
+       const struct addrinfo *rp;
+       *has_v4only = false;
+       *has_v6only = false;
+
+       for (host_idx = 0; host_idx < result_count; host_idx++) {
+               bool has_v4 = false;
+               bool has_v6 = false;
+               for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
+                       if (rp->ai_family == AF_INET6)
+                               has_v6 = true;
+                       else
+                               has_v4 = true;
+               }
+               if (has_v4 && !has_v6)
+                       *has_v4only = true;
+               else if (has_v6 && !has_v4)
+                       *has_v6only = true;
        }
 }

@@ -636,13 +663,16 @@
 {
        size_t host_idx;
        struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
+       const struct addrinfo *rp;

        for (host_idx = 0; host_idx < result_count; host_idx++) {
-               if (result[host_idx]->ai_family != AF_INET6)
-                       continue;
-               if (memcmp(&((struct sockaddr_in6 
*)result[host_idx]->ai_addr)->sin6_addr,
-                          &in6addr_any, sizeof(in6addr_any)) == 0)
-                       return true;
+               for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
+                       if (rp->ai_family != AF_INET6)
+                               continue;
+                       if (memcmp(&((struct sockaddr_in6 
*)rp->ai_addr)->sin6_addr,
+                               &in6addr_any, sizeof(in6addr_any)) == 0)
+                               return true;
+               }
        }
        return false;
 }
@@ -676,22 +706,28 @@
        for (host_idx = 0; host_idx < host_cont; host_idx++) {
                /* Addresses are ordered based on RFC 3484, see man getaddrinfo 
*/
                for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
-                       if (family != AF_UNSPEC && rp->ai_family != family)
-                               continue;
-                       if (offset + rp->ai_addrlen > addrs_buf_len) {
-                               LOGP(DLGLOBAL, LOGL_ERROR, "Output buffer to 
small: %zu\n",
-                                    addrs_buf_len);
-                               return -ENOSPC;
+                       if (family == AF_UNSPEC || rp->ai_family == family)
+                               break;
+               }
+               if (!rp && family == AF_INET6) {
+                       /* See if we can find an AF_INET addr for the AF_INET6 
socket instead: */
+                       for (rp = result[host_idx]; rp != NULL; rp = 
rp->ai_next) {
+                               if (rp->ai_family == AF_INET)
+                                       break;
                        }
-                       memcpy(addrs_buf + offset, rp->ai_addr, rp->ai_addrlen);
-                       offset += rp->ai_addrlen;
-                       break;
                }
                if (!rp) { /* No addr could be bound for this host! */
                        LOGP(DLGLOBAL, LOGL_ERROR, "No suitable remote address 
found for host: %s\n",
                             hosts[host_idx]);
                        return -ENODEV;
                }
+               if (offset + rp->ai_addrlen > addrs_buf_len) {
+                       LOGP(DLGLOBAL, LOGL_ERROR, "Output buffer to small: 
%zu\n",
+                               addrs_buf_len);
+                       return -ENOSPC;
+               }
+               memcpy(addrs_buf + offset, rp->ai_addr, rp->ai_addrlen);
+               offset += rp->ai_addrlen;
        }
        return 0;
 }
@@ -827,15 +863,13 @@
        struct addrinfo *res_loc[OSMO_SOCK_MAX_ADDRS], 
*res_rem[OSMO_SOCK_MAX_ADDRS];
        int sfd = -1, rc, on = 1;
        unsigned int i;
-       bool loc_has_v4addr, rem_has_v4addr;
-       bool loc_has_v6addr, rem_has_v6addr;
+       bool loc_has_v4addr = false, loc_has_v6addr = false;
+       bool rem_has_v4addr = false, rem_has_v6addr = false;
+       bool loc_has_v4only_addr, rem_has_v4only_addr;
+       bool loc_has_v6only_addr, rem_has_v6only_addr;
        struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS];
        char strbuf[512];

-       /* updated later in case of AF_UNSPEC */
-       loc_has_v4addr = rem_has_v4addr = (family == AF_INET);
-       loc_has_v6addr = rem_has_v6addr = (family == AF_INET6);
-
        /* TODO: So far this function is only aimed for SCTP, but could be
           reused in the future for other protocols with multi-addr support */
        if (proto != IPPROTO_SCTP)
@@ -862,10 +896,17 @@
                                           local_hosts_cnt, local_port, true);
                if (rc < 0)
                        return -EINVAL;
-               /* Figure out if there's any IPV4 or IPv6 addr in the set */
-               if (family == AF_UNSPEC)
-                       addrinfo_has_v4v6addr((const struct addrinfo 
**)res_loc, local_hosts_cnt,
-                                             &loc_has_v4addr, &loc_has_v6addr);
+               /* Figure out if there's any IPv4 or IPv6 entry in the result 
set */
+               addrinfo_has_v4v6addr((const struct addrinfo **)res_loc, 
local_hosts_cnt,
+                                      &loc_has_v4addr, &loc_has_v6addr);
+               /* Figure out if there's any IPv4-only or IPv6-only addr in the 
result set */
+               addrinfo_has_v4v6only_addr((const struct addrinfo **)res_loc, 
local_hosts_cnt,
+                                          &loc_has_v4only_addr, 
&loc_has_v6only_addr);
+               if (family == AF_INET && loc_has_v6only_addr) {
+                       LOGP(DLGLOBAL, LOGL_ERROR, "Cannot bind an IPv6 address 
to an AF_INET socket\n");
+                       rc = -EINVAL;
+                       goto ret_freeaddrinfo;
+               }
        }
        /* figure out remote side of socket */
        if (flags & OSMO_SOCK_F_CONNECT) {
@@ -875,26 +916,44 @@
                        rc = -EINVAL;
                        goto ret_freeaddrinfo_loc;
                }
-               /* Figure out if there's any IPv4 or IPv6  addr in the set */
-               if (family == AF_UNSPEC)
-                       addrinfo_has_v4v6addr((const struct addrinfo 
**)res_rem, remote_hosts_cnt,
-                                             &rem_has_v4addr, &rem_has_v6addr);
+               /* Figure out if there's any IPv4 or IPv6 entry in the result 
set */
+               addrinfo_has_v4v6addr((const struct addrinfo **)res_rem, 
remote_hosts_cnt,
+                                      &rem_has_v4addr, &rem_has_v6addr);
+               /* Figure out if there's any IPv4-only or IPv6-only addr in the 
result set */
+               addrinfo_has_v4v6only_addr((const struct addrinfo **)res_rem, 
remote_hosts_cnt,
+                                          &rem_has_v4only_addr, 
&rem_has_v6only_addr);
+               if (family == AF_INET && rem_has_v6only_addr) {
+                       LOGP(DLGLOBAL, LOGL_ERROR, "Cannot connect to an IPv6 
address in an AF_INET socket\n");
+                       rc = -EINVAL;
+                       goto ret_freeaddrinfo;
+               }
        }

-       if (((flags & OSMO_SOCK_F_BIND) && (flags & OSMO_SOCK_F_CONNECT)) &&
-           !addrinfo_has_in6addr_any((const struct addrinfo **)res_loc, 
local_hosts_cnt) &&
+       /* Find out the socket family now if not established by caller:
+        * Both are checked here through "or" here to account for "bind flag 
set,
+        * connect flag not set" and viceversa. */
+       if (family == AF_UNSPEC) {
+               if (!loc_has_v6addr && !rem_has_v6addr)
+                       family = AF_INET;
+               else
+                       family = AF_INET6;
+       }
+
+       /* if both sets are used, make sure there's at least 1 address of the
+        * same type on each set so that SCTP INIT/INIT-ACK can work. */
+       if (family == AF_INET6 && ((flags & OSMO_SOCK_F_BIND) && (flags & 
OSMO_SOCK_F_CONNECT)) &&
            (loc_has_v4addr != rem_has_v4addr || loc_has_v6addr != 
rem_has_v6addr)) {
-               LOGP(DLGLOBAL, LOGL_ERROR, "Invalid v4 vs v6 in local vs remote 
addresses: "
-                    "local:%s%s remote:%s%s\n",
-                    loc_has_v4addr ? " v4" : "", loc_has_v6addr ? " v6" : "",
-                    rem_has_v4addr ? " v4" : "", rem_has_v6addr ? " v6" : ""
-                    );
-               rc = -EINVAL;
-               goto ret_freeaddrinfo;
+               if (!addrinfo_has_in6addr_any((const struct addrinfo 
**)res_loc, local_hosts_cnt)) {
+                       LOGP(DLGLOBAL, LOGL_ERROR, "Invalid v4 vs v6 in local 
vs remote addresses: "
+                            "local:%s%s remote:%s%s\n",
+                            loc_has_v4addr ? " v4" : "", loc_has_v6addr ? " 
v6" : "",
+                            rem_has_v4addr ? " v4" : "", rem_has_v6addr ? " 
v6" : "");
+                       rc = -EINVAL;
+                       goto ret_freeaddrinfo;
+               }
        }

-       sfd = socket_helper_multiaddr(loc_has_v6addr ? AF_INET6 : AF_INET,
-                                     type, proto, flags);
+       sfd = socket_helper_multiaddr(family, type, proto, flags);
        if (sfd < 0) {
                rc = sfd;
                goto ret_freeaddrinfo;

--
To view, visit https://gerrit.osmocom.org/c/libosmocore/+/35232?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings

Gerrit-Project: libosmocore
Gerrit-Branch: master
Gerrit-Change-Id: I2641fbaca6f477404b094dbc53c0c1a3dd3fd2fd
Gerrit-Change-Number: 35232
Gerrit-PatchSet: 3
Gerrit-Owner: pespin <pes...@sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <lafo...@osmocom.org>
Gerrit-Reviewer: osmith <osm...@sysmocom.de>
Gerrit-Reviewer: pespin <pes...@sysmocom.de>
Gerrit-MessageType: merged

Reply via email to