diff options
author | Pau Espin Pedrol <pespin@sysmocom.de> | 2023-12-05 18:28:28 +0100 |
---|---|---|
committer | Pau Espin Pedrol <pespin@sysmocom.de> | 2023-12-06 13:36:48 +0100 |
commit | 1c797fca795be67f398c7fd6d3d7d399b31c6c8e (patch) | |
tree | baaed1d6108c4fce47822be95a0483a27357a4f5 | |
parent | 5b4135cf637930dbd478a93785707d606ed7a7e1 (diff) |
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
-rw-r--r-- | src/core/socket.c | 147 |
1 files changed, 103 insertions, 44 deletions
diff --git a/src/core/socket.c b/src/core/socket.c index 1dc8e462..0bc275b0 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -616,18 +616,45 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto, #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 @@ static bool addrinfo_has_in6addr_any(const struct addrinfo **result, size_t resu { 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 @@ static int addrinfo_to_sockaddr(uint16_t family, const struct addrinfo **result, 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 @@ int osmo_sock_init2_multiaddr2(uint16_t family, uint16_t type, uint8_t proto, 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 @@ int osmo_sock_init2_multiaddr2(uint16_t family, uint16_t type, uint8_t proto, 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 @@ int osmo_sock_init2_multiaddr2(uint16_t family, uint16_t type, uint8_t proto, 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; + } + } + + /* 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 (((flags & OSMO_SOCK_F_BIND) && (flags & OSMO_SOCK_F_CONNECT)) && - !addrinfo_has_in6addr_any((const struct addrinfo **)res_loc, local_hosts_cnt) && + /* 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; |