mirror of https://gerrit.osmocom.org/libosmocore
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
This commit is contained in:
parent
c85f773640
commit
3f464fc007
11
configure.ac
11
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],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 \
|
||||
|
|
286
src/socket.c
286
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
|
||||
|
|
Loading…
Reference in New Issue