diff options
| author | Pau Espin Pedrol <pespin@sysmocom.de> | 2019-10-10 17:38:35 +0200 | 
|---|---|---|
| committer | laforge <laforge@osmocom.org> | 2019-10-18 09:21:48 +0000 | 
| commit | 3f464fc007ace5a829e8d43e72a88055f14b52e3 (patch) | |
| tree | d2785c51e0b50b3cc95dec011061a40479f4895c | |
| parent | c85f773640965066ce5488f0be291fc69065a50b (diff) | |
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
| -rw-r--r-- | configure.ac | 11 | ||||
| -rw-r--r-- | debian/control | 1 | ||||
| -rw-r--r-- | include/osmocom/core/socket.h | 7 | ||||
| -rw-r--r-- | src/Makefile.am | 4 | ||||
| -rw-r--r-- | src/socket.c | 286 | 
5 files changed, 307 insertions, 2 deletions
| diff --git a/configure.ac b/configure.ac index 39d232b3..2f12d861 100644 --- a/configure.ac +++ b/configure.ac @@ -100,6 +100,17 @@ AC_SUBST(SYMBOL_VISIBILITY)  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_WARN([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/debian/control b/debian/control index 07163da2..6c9cfae9 100644 --- a/debian/control +++ b/debian/control @@ -15,6 +15,7 @@ Build-Depends: debhelper (>= 9),                 libpcsclite-dev,                 pkg-config,                 libtalloc-dev, +               libsctp-dev,                 python (>= 2.7.6)  Standards-Version: 3.9.8  Vcs-Git: git://git.osmocom.org/libosmocore.git diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h index 37b1eaef..e26ca0d3 100644 --- a/include/osmocom/core/socket.h +++ b/include/osmocom/core/socket.h @@ -36,6 +36,9 @@ struct osmo_fd;  /*! 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 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,  		   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 5f5f0171..99432811 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 @@ endif  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 ef3bb58d..542c76e5 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 @@ static struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t  	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 @@ static int socket_helper(const struct addrinfo *rp, unsigned int flags)  	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 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,  	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 | 
