socket: Introduce APIs osmo_sock_multiaddr_{add,del}_local_addr()

These APIs are used to bind or unbind an active socket adding or
removing addresses from the existing set.

Related: OS#6077
Change-Id: Ifc6e7d643c2a0c53f479bfd0d5c36d08c0c01953
diff --git a/TODO-RELEASE b/TODO-RELEASE
index 8ccfa49..2f9903d 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -7,3 +7,4 @@
 # If any interfaces have been added since the last public release: c:r:a + 1.
 # If any interfaces have been removed or changed since the last public release: c:r:0.
 #library	what			description / commit summary line
+core      ADD       osmo_sock_multiaddr_{add,del}_local_addr()
\ No newline at end of file
diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h
index bbf828f..db55863 100644
--- a/include/osmocom/core/socket.h
+++ b/include/osmocom/core/socket.h
@@ -154,7 +154,6 @@
 		   const char **remote_hosts, size_t remote_hosts_cnt, uint16_t remote_port,
 		   unsigned int flags, struct osmo_sock_init2_multiaddr_pars *pars);
 
-
 int osmo_sock_init_osa(uint16_t type, uint8_t proto,
 		    const struct osmo_sockaddr *local,
 		    const struct osmo_sockaddr *remote,
@@ -191,6 +190,8 @@
 int osmo_sock_get_remote_ip(int fd, char *host, size_t len);
 int osmo_sock_get_remote_ip_port(int fd, char *port, size_t len);
 
+int osmo_sock_multiaddr_add_local_addr(int sfd, const char **addrs, size_t addrs_cnt);
+int osmo_sock_multiaddr_del_local_addr(int sfd, const char **addrs, size_t addrs_cnt);
 
 int osmo_sock_mcast_loop_set(int fd, bool enable);
 int osmo_sock_mcast_ttl_set(int fd, uint8_t ttl);
diff --git a/src/core/libosmocore.map b/src/core/libosmocore.map
index 30814c3..105a80a 100644
--- a/src/core/libosmocore.map
+++ b/src/core/libosmocore.map
@@ -431,6 +431,8 @@
 osmo_sock_mcast_loop_set;
 osmo_sock_mcast_subscribe;
 osmo_sock_mcast_ttl_set;
+osmo_sock_multiaddr_add_local_addr;
+çosmo_sock_multiaddr_del_local_addr;
 osmo_sock_set_dscp;
 osmo_sock_set_priority;
 osmo_sock_unix_init;
diff --git a/src/core/socket.c b/src/core/socket.c
index 024757f..2de3131 100644
--- a/src/core/socket.c
+++ b/src/core/socket.c
@@ -1257,6 +1257,158 @@
 	return osmo_sock_init(ss->sa_family, type, proto, host, port, flags);
 }
 
+/*! Add addresses to the multi-address (SCTP) socket active binding set
+ *  \param[in] sfd The multi-address (SCTP) socket
+ *  \param[in] addrs array of char pointers (strings), each containing local host name or IP address in string form
+ *  \param[in] addrs_cnt length of addrs_hosts (in items)
+ *  \returns 0 on success; negative on error
+ *
+ * This function only supports SCTP sockets so far, and hence it should be
+ * called only on socket file descriptions referencing that kind of sockets.
+ */
+int osmo_sock_multiaddr_add_local_addr(int sfd, const char **addrs, size_t addrs_cnt)
+{
+	struct osmo_sockaddr osa;
+	socklen_t slen = sizeof(osa);
+	uint16_t sfd_family;
+	uint16_t type = SOCK_STREAM ;/* Fixme: we assume fd is SOCK_STREAM */
+	uint8_t proto = IPPROTO_SCTP; /* Fixme: we assume fd is IPPROTO_SCTP */
+	struct addrinfo *res[OSMO_SOCK_MAX_ADDRS];
+	uint16_t port;
+	struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS];
+	unsigned int i;
+	int rc;
+	bool res_has_v4addr = false, res_has_v6addr = false;
+
+	rc = getsockname(sfd, &osa.u.sa, &slen);
+	if (rc < 0)
+		return rc; /* TODO: log error? */
+	sfd_family = osa.u.sa.sa_family;
+	port = osmo_sockaddr_port(&osa.u.sa);
+
+	if (sfd_family != AF_INET && sfd_family != AF_INET6)
+		return -EINVAL;
+
+	rc = addrinfo_helper_multi(res, AF_UNSPEC, type, proto, addrs,
+				   addrs_cnt, port, true);
+	if (rc < 0)
+		return -EINVAL;
+
+	addrinfo_has_v4v6addr((const struct addrinfo **)res, addrs_cnt,
+			      &res_has_v4addr, &res_has_v6addr);
+	if (sfd_family == AF_INET && !res_has_v4addr) {
+		rc = -EINVAL;
+		goto ret_free;
+	}
+
+	uint16_t new_addr_family;
+	if (sfd_family == AF_INET)
+		new_addr_family = AF_INET;
+	else if (sfd_family == AF_INET6 && !res_has_v4addr)
+		new_addr_family = AF_INET6;
+	else
+		new_addr_family = AF_UNSPEC;
+	rc = addrinfo_to_sockaddr(new_addr_family, (const struct addrinfo **)res,
+				  addrs, addrs_cnt,
+				  (uint8_t *)addrs_buf, sizeof(addrs_buf));
+	if (rc < 0) {
+		rc = -ENODEV;
+		goto ret_free;
+	}
+
+	rc = sctp_bindx(sfd, (struct sockaddr *)addrs_buf, addrs_cnt, SCTP_BINDX_ADD_ADDR);
+	if (rc == -1) {
+		int err = errno;
+		char strbuf[512];
+		multiaddr_snprintf(strbuf, sizeof(strbuf), addrs, addrs_cnt);
+		LOGP(DLGLOBAL, LOGL_NOTICE, "Unable to bind socket to new addresses: %s:%u: %s\n",
+			strbuf, port, strerror(err));
+		rc = -ENODEV;
+		goto ret_free;
+	}
+
+ret_free:
+	for (i = 0; i < addrs_cnt; i++)
+		freeaddrinfo(res[i]);
+	return rc;
+}
+
+/*! Remove addresses from the multi-address (SCTP) socket active binding set
+ *  \param[in] sfd The multi-address (SCTP) socket
+ *  \param[in] addrs array of char pointers (strings), each containing local host name or IP address in string form
+ *  \param[in] addrs_cnt length of addrs_hosts (in items)
+ *  \returns 0 on success; negative on error
+ *
+ * This function only supports SCTP sockets so far, and hence it should be
+ * called only on socket file descriptions referencing that kind of sockets.
+ */
+int osmo_sock_multiaddr_del_local_addr(int sfd, const char **addrs, size_t addrs_cnt)
+{
+	struct osmo_sockaddr osa;
+	socklen_t slen = sizeof(osa);
+	uint16_t sfd_family;
+	uint16_t type = SOCK_STREAM ;/* Fixme: we assume fd is SOCK_STREAM */
+	uint8_t proto = IPPROTO_SCTP; /* Fixme: we assume fd is IPPROTO_SCTP */
+	struct addrinfo *res[OSMO_SOCK_MAX_ADDRS];
+	uint16_t port;
+	struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS];
+	unsigned int i;
+	int rc;
+	bool res_has_v4addr = false, res_has_v6addr = false;
+
+	rc = getsockname(sfd, &osa.u.sa, &slen);
+	if (rc < 0)
+		return rc; /* TODO: log error? */
+	sfd_family = osa.u.sa.sa_family;
+	port = osmo_sockaddr_port(&osa.u.sa);
+
+	if (sfd_family != AF_INET && sfd_family != AF_INET6)
+		return -EINVAL;
+
+	rc = addrinfo_helper_multi(res, AF_UNSPEC, type, proto, addrs,
+				   addrs_cnt, port, true);
+	if (rc < 0)
+		return -EINVAL;
+
+	addrinfo_has_v4v6addr((const struct addrinfo **)res, addrs_cnt,
+			      &res_has_v4addr, &res_has_v6addr);
+	if (sfd_family == AF_INET && !res_has_v4addr) {
+		rc = -EINVAL;
+		goto ret_free;
+	}
+
+	uint16_t del_addr_family;
+	if (sfd_family == AF_INET)
+		del_addr_family = AF_INET;
+	else if (sfd_family == AF_INET6 && !res_has_v4addr)
+		del_addr_family = AF_INET6;
+	else
+		del_addr_family = AF_UNSPEC;
+	rc = addrinfo_to_sockaddr(del_addr_family, (const struct addrinfo **)res,
+				  addrs, addrs_cnt,
+				  (uint8_t *)addrs_buf, sizeof(addrs_buf));
+	if (rc < 0) {
+		rc = -ENODEV;
+		goto ret_free;
+	}
+
+	rc = sctp_bindx(sfd, (struct sockaddr *)addrs_buf, addrs_cnt, SCTP_BINDX_REM_ADDR);
+	if (rc == -1) {
+		int err = errno;
+		char strbuf[512];
+		multiaddr_snprintf(strbuf, sizeof(strbuf), addrs, addrs_cnt);
+		LOGP(DLGLOBAL, LOGL_NOTICE, "Unable to unbind socket from addresses: %s:%u: %s\n",
+			strbuf, port, strerror(err));
+		rc = -ENODEV;
+		goto ret_free;
+	}
+
+ret_free:
+	for (i = 0; i < addrs_cnt; i++)
+		freeaddrinfo(res[i]);
+	return rc;
+}
+
 static int sockaddr_equal(const struct sockaddr *a,
 			  const struct sockaddr *b, unsigned int len)
 {