Change in libosmocore[master]: socket: Introduce API osmo_sock_init2_multiaddr()

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

pespin gerrit-no-reply at lists.osmocom.org
Fri Oct 11 19:10:28 UTC 2019


pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/libosmocore/+/15781 )


Change subject: socket: Introduce API osmo_sock_init2_multiaddr()
......................................................................

socket: Introduce API osmo_sock_init2_multiaddr()

This API will be used by libosmo-netif's osmo_stream for SCTP sockets,
which in turn will be used by libosmo-sccp to support multi-homed
connections.

Related: OS#3608
Change-Id: Ic8681d9e093216c99c6bca4be81c31ef83688ed1
---
M configure.ac
M include/osmocom/core/socket.h
M src/Makefile.am
M src/socket.c
4 files changed, 306 insertions(+), 2 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/libosmocore refs/changes/81/15781/1

diff --git a/configure.ac b/configure.ac
index 39d232b..2aefd2c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -100,6 +100,17 @@
 
 AC_CHECK_FUNCS(clock_gettime localtime_r)
 
+old_LIBS=$LIBS
+AC_SEARCH_LIBS([sctp_bindx], [sctp], [
+	AC_DEFINE(HAVE_LIBSCTP, 1, [Define 1 to enable SCTP support])
+	AC_SUBST(HAVE_LIBSCTP, [1])
+	if test -n "$ac_lib"; then
+		AC_SUBST(LIBSCTP_LIBS, [-l$ac_lib])
+	fi
+	], [
+	AC_MSG_ERROR([sctp_bindx not found in searched libs])])
+LIBS=$old_LIBS
+
 AC_DEFUN([CHECK_TM_INCLUDES_TM_GMTOFF], [
   AC_CACHE_CHECK(
     [whether struct tm has tm_gmtoff member],
diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h
index 37b1eae..e26ca0d 100644
--- a/include/osmocom/core/socket.h
+++ b/include/osmocom/core/socket.h
@@ -36,6 +36,9 @@
 /*! use SO_REUSEADDR on UDP ports (required for multicast) */
 #define OSMO_SOCK_F_UDP_REUSEADDR (1 << 5)
 
+/*! maximum number of local or remote addresses supported by an osmo_sock instance */
+#define OSMO_SOCK_MAX_ADDRS 32
+
 int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
 		   const char *host, uint16_t port, unsigned int flags);
 
@@ -43,6 +46,10 @@
 		   const char *local_host, uint16_t local_port,
 		   const char *remote_host, uint16_t remote_port, unsigned int flags);
 
+int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
+		   const char **local_hosts, size_t local_hosts_cnt, uint16_t local_port,
+		   const char **remote_hosts, size_t remote_hosts_cnt, uint16_t remote_port, unsigned int flags);
+
 int osmo_sock_init_ofd(struct osmo_fd *ofd, int family, int type, int proto,
 			const char *host, uint16_t port, unsigned int flags);
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 5f5f017..9943281 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,7 +4,7 @@
 LIBVERSION=14:0:2
 
 AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS)
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS) $(LIBSCTP_CFLAGS)
 
 if ENABLE_PSEUDOTALLOC
 AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc
@@ -12,7 +12,7 @@
 
 lib_LTLIBRARIES = libosmocore.la
 
-libosmocore_la_LIBADD = $(BACKTRACE_LIB) $(TALLOC_LIBS) $(LIBRARY_RT) $(PTHREAD_LIBS)
+libosmocore_la_LIBADD = $(BACKTRACE_LIB) $(TALLOC_LIBS) $(LIBRARY_RT) $(PTHREAD_LIBS) $(LIBSCTP_LIBS)
 libosmocore_la_SOURCES = context.c timer.c timer_gettimeofday.c timer_clockgettime.c \
 			 select.c signal.c msgb.c bits.c \
 			 bitvec.c bitcomp.c counter.c fsm.c \
diff --git a/src/socket.c b/src/socket.c
index ef3bb58..542c76e 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -53,6 +53,10 @@
 #include <netdb.h>
 #include <ifaddrs.h>
 
+#ifdef HAVE_LIBSCTP
+#include <netinet/sctp.h>
+#endif
+
 static struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t proto,
 					const char *host, uint16_t port, bool passive)
 {
@@ -96,6 +100,34 @@
 	return result;
 }
 
+/*! Retrieve an array of addrinfo with specified hints, one for each host in the hosts array.
+ *  \param[out] addrinfo array of addrinfo pointers, will be filled by the function on success.
+ *		Its size must be at least the one of hosts.
+ *  \param[in] family Socket family like AF_INET, AF_INET6.
+ *  \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM.
+ *  \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP.
+ *  \param[in] hosts array of char pointers (strings) containing the addresses to query.
+ *  \param[in] host_cnt length of the hosts array (in items).
+ *  \param[in] port port number in host byte order.
+ *  \param[in] passive whether to include the AI_PASSIVE flag in getaddrinfo() hints.
+ *  \returns 0 is returned on success together with a filled addrinfo array; negative on error
+ */
+static int addrinfo_helper_multi(struct addrinfo **addrinfo, uint16_t family, uint16_t type, uint8_t proto,
+					const char **hosts, size_t host_cnt, uint16_t port, bool passive)
+{
+	int i, j;
+
+	for (i = 0; i < host_cnt; i++) {
+		addrinfo[i] = addrinfo_helper(family, type, proto, hosts[i], port, passive);
+		if (!addrinfo[i]) {
+			for (j = 0; j < i; j++)
+				freeaddrinfo(addrinfo[j]);
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
 static int socket_helper(const struct addrinfo *rp, unsigned int flags)
 {
 	int sfd, on = 1;
@@ -118,6 +150,37 @@
 	return sfd;
 }
 
+/* Fill buf with a string representation of the address set, in the form:
+ * buf_len == 0: "()"
+ * buf_len == 1: "hostA"
+ * buf_len >= 2: (hostA|hostB|...|...)
+ */
+static int multiaddr_snprintf(char* buf, size_t buf_len, const char **hosts, size_t host_cnt)
+{
+	int len = 0, offset = 0, rem = buf_len;
+	int ret, i;
+	char *after;
+
+	if (buf_len < 3)
+		return -EINVAL;
+
+	if (host_cnt != 1) {
+		ret = snprintf(buf, rem, "(");
+		if (ret < 0)
+			return ret;
+		OSMO_SNPRINTF_RET(ret, rem, offset, len);
+	}
+	for (i = 0; i < host_cnt; i++) {
+		if (host_cnt == 1)
+			after = "";
+		else
+			after = (i == (host_cnt - 1)) ? ")" : "|";
+		ret = snprintf(buf + offset, rem, "%s%s", hosts[i] ? : "0.0.0.0", after);
+		OSMO_SNPRINTF_RET(ret, rem, offset, len);
+	}
+
+	return len;
+}
 
 static int osmo_sock_init_tail(int fd, uint16_t type, unsigned int flags)
 {
@@ -294,6 +357,229 @@
 	return sfd;
 }
 
+#ifdef HAVE_LIBSCTP
+
+
+/* Build array of addresses taking first addrinfo result of the requested family
+ * for each host in hosts. addrs4 or addrs6 are filled based on family type. */
+static int addrinfo_to_sockaddr(uint16_t family, const struct addrinfo **result,
+				const char **hosts, int host_cont,
+				struct sockaddr_in *addrs4, struct sockaddr_in6 *addrs6) {
+	size_t host_idx;
+	const struct addrinfo *rp;
+	OSMO_ASSERT(family == AF_INET || family == AF_INET6);
+
+	for (host_idx = 0; host_idx < host_cont; host_idx++) {
+		for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
+			if (rp->ai_family != family)
+				continue;
+			if (family == AF_INET)
+				memcpy(&addrs4[host_idx], rp->ai_addr, sizeof(addrs4[host_idx]));
+			else
+				memcpy(&addrs6[host_idx], rp->ai_addr, sizeof(addrs6[host_idx]));
+			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;
+		}
+	}
+	return 0;
+}
+
+/*! Initialize a socket (including bind and/or connect) with multiple local or remote addresses.
+ *  \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
+ *  \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
+ *  \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
+ *  \param[in] local_hosts array of char pointers (strings), each containing local host name or IP address in string form
+ *  \param[in] local_hosts_cnt length of local_hosts (in items)
+ *  \param[in] local_port local port number in host byte order
+ *  \param[in] remote_host array of char pointers (strings), each containing remote host name or IP address in string form
+ *  \param[in] remote_hosts_cnt length of remote_hosts (in items)
+ *  \param[in] remote_port remote port number in host byte order
+ *  \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
+ *  \returns socket file descriptor on success; negative on error
+ *
+ * This function is similar to \ref osmo_sock_init2(), but can be passed an
+ * array of local or remote addresses for protocols supporting multiple
+ * addresses per socket, like SCTP (currently only one supported). This function
+ * should not be used by protocols not supporting this kind of features, but
+ * rather \ref osmo_sock_init2() should be used instead.
+ * See \ref osmo_sock_init2() for more information on flags and general behavior.
+ */
+int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
+		   const char **local_hosts, size_t local_hosts_cnt, uint16_t local_port,
+		   const char **remote_hosts, size_t remote_hosts_cnt, uint16_t remote_port,
+		   unsigned int flags)
+
+{
+	struct addrinfo *result[OSMO_SOCK_MAX_ADDRS];
+	int sfd = -1, rc, on = 1;
+	int i;
+	struct sockaddr_in addrs4[OSMO_SOCK_MAX_ADDRS];
+	struct sockaddr_in6 addrs6[OSMO_SOCK_MAX_ADDRS];
+	struct sockaddr *addrs;
+	char strbuf[512];
+
+	/* 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)
+		return -ENOTSUP;
+
+	/* TODO: Let's not support AF_UNSPEC for now. sctp_bindx() actually
+	   supports binding both types of addresses on a AF_INET6 soscket, but
+	   that would mean we could get both AF_INET and AF_INET6 addresses for
+	   each host, and makes complexity of this function increase a lot since
+	   we'd need to find out which subsets to use, use v4v6 mapped socket,
+	   etc. */
+	if (family == AF_UNSPEC)
+		return -ENOTSUP;
+
+	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;
+	}
+
+	if (((flags & OSMO_SOCK_F_BIND) && !local_hosts_cnt) ||
+	    ((flags & OSMO_SOCK_F_CONNECT) && !remote_hosts_cnt) ||
+	    local_hosts_cnt > OSMO_SOCK_MAX_ADDRS ||
+	    remote_hosts_cnt > OSMO_SOCK_MAX_ADDRS)
+		return -EINVAL;
+
+	/* figure out local side of socket */
+	if (flags & OSMO_SOCK_F_BIND) {
+		rc = addrinfo_helper_multi(result, family, type, proto, local_hosts,
+					       local_hosts_cnt, local_port, true);
+		if (rc < 0)
+			return -EINVAL;
+
+		/* Since addrinfo_helper sets ai_family, socktype and
+		   ai_protocol in hints, we know all results will use same
+		   values, so simply pick the first one and pass it to create
+		   the socket:
+		*/
+		sfd = socket_helper(result[0], flags);
+		if (sfd < 0) {
+			for (i = 0; i < local_hosts_cnt; i++)
+				freeaddrinfo(result[i]);
+			return sfd;
+		}
+
+		if (proto != IPPROTO_UDP || flags & OSMO_SOCK_F_UDP_REUSEADDR) {
+			rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
+					&on, sizeof(on));
+			if (rc < 0) {
+				multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
+				LOGP(DLGLOBAL, LOGL_ERROR,
+				     "cannot setsockopt socket:"
+				     " %s:%u: %s\n",
+				     strbuf, local_port,
+				     strerror(errno));
+				for (i = 0; i < local_hosts_cnt; i++)
+					freeaddrinfo(result[i]);
+				close(sfd);
+				return rc;
+			}
+		}
+
+		/* Build array of addresses taking first of same family for each host.
+		   TODO: Ideally we should use backtracking storing last used
+		   indexes and trying next combination if connect() fails .*/
+		rc = addrinfo_to_sockaddr(family, (const struct addrinfo **)result,
+					  local_hosts, local_hosts_cnt, addrs4, addrs6);
+		if (rc < 0) {
+			for (i = 0; i < local_hosts_cnt; i++)
+				freeaddrinfo(result[i]);
+			close(sfd);
+			return -ENODEV;
+		}
+
+		if (family == AF_INET)
+			addrs = (struct sockaddr *)addrs4;
+		else
+			addrs = (struct sockaddr *)addrs6;
+		if (sctp_bindx(sfd, addrs, local_hosts_cnt, SCTP_BINDX_ADD_ADDR) == -1) {
+			multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
+			LOGP(DLGLOBAL, LOGL_NOTICE, "unable to bind socket: %s:%u: %s\n",
+			     strbuf, local_port, strerror(errno));
+			for (i = 0; i < local_hosts_cnt; i++)
+			     freeaddrinfo(result[i]);
+			close(sfd);
+			return -ENODEV;
+		}
+		for (i = 0; i < local_hosts_cnt; i++)
+			freeaddrinfo(result[i]);
+	}
+
+	/* Reached this point, if OSMO_SOCK_F_BIND then sfd is valid (>=0) or it
+	   was already closed and func returned. If OSMO_SOCK_F_BIND is not
+	   set, then sfd = -1 */
+
+	/* figure out remote side of socket */
+	if (flags & OSMO_SOCK_F_CONNECT) {
+		rc = addrinfo_helper_multi(result, family, type, proto, remote_hosts,
+					       remote_hosts_cnt, remote_port, false);
+		if (rc < 0) {
+			if (sfd >= 0)
+				close(sfd);
+			return -EINVAL;
+		}
+
+		if (sfd < 0) {
+			/* Since addrinfo_helper sets ai_family, socktype and
+			   ai_protocol in hints, we know all results will use same
+			   values, so simply pick the first one and pass it to create
+			   the socket:
+			*/
+			sfd = socket_helper(result[0], flags);
+			if (sfd < 0) {
+				for (i = 0; i < remote_hosts_cnt; i++)
+					freeaddrinfo(result[i]);
+				return sfd;
+			}
+		}
+
+		/* Build array of addresses taking first of same family for each host.
+		   TODO: Ideally we should use backtracking storing last used
+		   indexes and trying next combination if connect() fails .*/
+		rc = addrinfo_to_sockaddr(family, (const struct addrinfo **)result,
+					  remote_hosts, remote_hosts_cnt, addrs4, addrs6);
+		if (rc < 0) {
+			for (i = 0; i < remote_hosts_cnt; i++)
+				freeaddrinfo(result[i]);
+			close(sfd);
+			return -ENODEV;
+		}
+
+		if (family == AF_INET)
+			addrs = (struct sockaddr *)addrs4;
+		else
+			addrs = (struct sockaddr *)addrs6;
+		rc = sctp_connectx(sfd, addrs, remote_hosts_cnt, NULL);
+		if (rc != 0 && errno != EINPROGRESS) {
+			multiaddr_snprintf(strbuf, sizeof(strbuf), remote_hosts, remote_hosts_cnt);
+			LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n",
+				strbuf, remote_port, strerror(errno));
+			for (i = 0; i < remote_hosts_cnt; i++)
+				freeaddrinfo(result[i]);
+			close(sfd);
+			return -ENODEV;
+		}
+		for (i = 0; i < remote_hosts_cnt; i++)
+			freeaddrinfo(result[i]);
+	}
+
+	rc = osmo_sock_init_tail(sfd, type, flags);
+	if (rc < 0) {
+		close(sfd);
+		sfd = -1;
+	}
+
+	return sfd;
+}
+#endif /* HAVE_LIBSCTP */
 
 /*! Initialize a socket (including bind/connect)
  *  \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC

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

Gerrit-Project: libosmocore
Gerrit-Branch: master
Gerrit-Change-Id: Ic8681d9e093216c99c6bca4be81c31ef83688ed1
Gerrit-Change-Number: 15781
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin at sysmocom.de>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20191011/2ab2ed01/attachment.htm>


More information about the gerrit-log mailing list