[PATCH] libosmocore[master]: Add osmo_sock_init2() function, allowing both BIND *and* CON...

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/.

Harald Welte gerrit-no-reply at lists.osmocom.org
Sat Apr 8 19:26:43 UTC 2017


Review at  https://gerrit.osmocom.org/2250

Add osmo_sock_init2() function, allowing both BIND *and* CONNECT

The old osmo_sock_init() function allows only either a bind (for a
server socket), or a connect (for a client socket), but not both
together.  So there's no way to have a client socket that is bound to a
specific local IP and/or port, which is needed for some use cases.

Change-Id: Idab124bcca47872f55311a82d6818aed590965e6
---
M include/osmocom/core/socket.h
M src/socket.c
M tests/socket/socket_test.c
M tests/socket/socket_test.err
M tests/socket/socket_test.ok
5 files changed, 243 insertions(+), 33 deletions(-)


  git pull ssh://gerrit.osmocom.org:29418/libosmocore refs/changes/50/2250/1

diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h
index 4f00e30..e19e8f2 100644
--- a/include/osmocom/core/socket.h
+++ b/include/osmocom/core/socket.h
@@ -24,6 +24,10 @@
 int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
 		   const char *host, uint16_t port, unsigned int flags);
 
+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_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/socket.c b/src/socket.c
index 2c1b547..ad0f69b 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -51,6 +51,188 @@
 #include <netdb.h>
 #include <ifaddrs.h>
 
+static struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t proto,
+					const char *host, uint16_t port, bool passive)
+{
+	struct addrinfo hints, *result;
+	char portbuf[16];
+	int rc;
+
+	snprintf(portbuf, sizeof(portbuf), "%u", port);
+	memset(&hints, 0, sizeof(struct addrinfo));
+	hints.ai_family = family;
+	if (type == SOCK_RAW) {
+		/* Workaround for glibc, that returns EAI_SERVICE (-8) if
+		 * SOCK_RAW and IPPROTO_GRE is used.
+		 */
+		hints.ai_socktype = SOCK_DGRAM;
+		hints.ai_protocol = IPPROTO_UDP;
+	} else {
+		hints.ai_socktype = type;
+		hints.ai_protocol = proto;
+	}
+
+	if (passive)
+		hints.ai_flags |= AI_PASSIVE;
+
+	rc = getaddrinfo(host, portbuf, &hints, &result);
+	if (rc != 0) {
+		LOGP(DLGLOBAL, LOGL_ERROR, "getaddrinfo returned NULL: %s:%u: %s\n",
+			host, port, strerror(errno));
+		return NULL;
+	}
+
+	return result;
+}
+
+static int socket_helper(const struct addrinfo *rp, unsigned int flags)
+{
+	int sfd, on = 1;
+
+	sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+	if (sfd == -1)
+		return sfd;
+	if (flags & OSMO_SOCK_F_NONBLOCK) {
+		if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
+			LOGP(DLGLOBAL, LOGL_ERROR,
+				"cannot set this socket unblocking: %s\n",
+				strerror(errno));
+			close(sfd);
+			sfd = -EINVAL;
+		}
+	}
+	return sfd;
+}
+
+
+/*! \brief Initialize a socket (including bind and/or connect)
+ *  \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_host local host name or IP address in string form
+ *  \param[in] local_port local port number in host byte order
+ *  \param[in] remote_host remote host name or IP address in string form
+ *  \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 creates a new socket of the designated \a family, \a
+ * type and \a proto and optionally binds it to the \a local_host and \a
+ * local_port as well as optionally connects it to the \a remote_host
+ * and \q remote_port, depending on the value * of \a flags parameter.
+ *
+ * As opposed to \ref osmo_sock_init(), this function allows to combine
+ * the \ref OSMO_SOCK_F_BIND and \ref OSMO_SOCK_F_CONNECT flags.  This
+ * is useful if you want to connect to a remote host/port, but still
+ * want to bind that socket to either a specific local alias IP and/or a
+ * specific local source port.
+ *
+ * You must specify either \ref OSMO_SOCK_F_BIND, or \ref
+ * OSMO_SOCK_F_CONNECT, or both.
+ *
+ * If \ref OSMO_SOCK_F_NONBLOCK is specified, the socket will be set to
+ * non-blocking mode.
+ */
+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)
+{
+	struct addrinfo *result, *rp;
+	int sfd = -1, rc, on = 1;
+
+	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;
+	}
+
+	/* figure out local side of socket */
+	if (flags & OSMO_SOCK_F_BIND) {
+		result = addrinfo_helper(family, type, proto, local_host, local_port, true);
+		if (!result)
+			return -EINVAL;
+
+		for (rp = result; rp != NULL; rp = rp->ai_next) {
+			/* Workaround for glibc again */
+			if (type == SOCK_RAW) {
+				rp->ai_socktype = SOCK_RAW;
+				rp->ai_protocol = proto;
+			}
+
+			sfd = socket_helper(rp, flags);
+			if (sfd < 0)
+				continue;
+
+			rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
+							&on, sizeof(on));
+			if (rc < 0) {
+				LOGP(DLGLOBAL, LOGL_ERROR,
+					"cannot setsockopt socket:"
+					" %s:%u: %s\n",
+					local_host, local_port, strerror(errno));
+				break;
+			}
+			if (bind(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
+				break;
+			close(sfd);
+		}
+		freeaddrinfo(result);
+		if (rp == NULL) {
+			LOGP(DLGLOBAL, LOGL_ERROR, "unable to bind socket: %s:%u: %s\n",
+				local_host, local_port, strerror(errno));
+			return -ENODEV;
+		}
+	}
+
+	/* figure out remote side of socket */
+	if (flags & OSMO_SOCK_F_CONNECT) {
+		result = addrinfo_helper(family, type, proto, remote_host, remote_port, false);
+		if (!result) {
+			close(sfd);
+			return -EINVAL;
+		}
+
+		for (rp = result; rp != NULL; rp = rp->ai_next) {
+			/* Workaround for glibc again */
+			if (type == SOCK_RAW) {
+				rp->ai_socktype = SOCK_RAW;
+				rp->ai_protocol = proto;
+			}
+
+			if (!sfd) {
+				sfd = socket_helper(rp, flags);
+				if (sfd < 0)
+					continue;
+			}
+
+			rc = connect(sfd, rp->ai_addr, rp->ai_addrlen);
+			if (rc != -1 || (rc == -1 && errno == EINPROGRESS))
+				break;
+
+			close(sfd);
+			sfd = -1;
+		}
+		freeaddrinfo(result);
+		if (rp == NULL) {
+			LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n",
+				remote_host, remote_port, strerror(errno));
+			return -ENODEV;
+		}
+	}
+
+	/* Make sure to call 'listen' on a bound, connection-oriented sock */
+	if ((flags & (OSMO_SOCK_F_BIND|OSMO_SOCK_F_CONNECT)) == OSMO_SOCK_F_BIND) {
+		switch (type) {
+		case SOCK_STREAM:
+		case SOCK_SEQPACKET:
+			listen(sfd, 10);
+			break;
+		}
+	}
+	return sfd;
+}
+
+
 /*! \brief Initialize a socket (including bind/connect)
  *  \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
  *  \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
@@ -67,9 +249,8 @@
 int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
 		   const char *host, uint16_t port, unsigned int flags)
 {
-	struct addrinfo hints, *result, *rp;
+	struct addrinfo *result, *rp;
 	int sfd, rc, on = 1;
-	char portbuf[16];
 
 	if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) ==
 		     (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) {
@@ -78,25 +259,8 @@
 		return -EINVAL;
 	}
 
-	sprintf(portbuf, "%u", port);
-	memset(&hints, 0, sizeof(struct addrinfo));
-	hints.ai_family = family;
-	if (type == SOCK_RAW) {
-		/* Workaround for glibc, that returns EAI_SERVICE (-8) if
-		 * SOCK_RAW and IPPROTO_GRE is used.
-		 */
-		hints.ai_socktype = SOCK_DGRAM;
-		hints.ai_protocol = IPPROTO_UDP;
-	} else {
-		hints.ai_socktype = type;
-		hints.ai_protocol = proto;
-	}
-
-	if (flags & OSMO_SOCK_F_BIND)
-		hints.ai_flags |= AI_PASSIVE;
-
-	rc = getaddrinfo(host, portbuf, &hints, &result);
-	if (rc != 0) {
+	result = addrinfo_helper(family, type, proto, host, port, flags & OSMO_SOCK_F_BIND);
+	if (!result) {
 		LOGP(DLGLOBAL, LOGL_ERROR, "getaddrinfo returned NULL: %s:%u: %s\n",
 			host, port, strerror(errno));
 		return -EINVAL;
@@ -109,20 +273,10 @@
 			rp->ai_protocol = proto;
 		}
 
-		sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+		sfd = socket_helper(rp, flags);
 		if (sfd == -1)
 			continue;
-		if (flags & OSMO_SOCK_F_NONBLOCK) {
-			if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
-				LOGP(DLGLOBAL, LOGL_ERROR,
-					"cannot set this socket unblocking:"
-					" %s:%u: %s\n",
-					host, port, strerror(errno));
-				close(sfd);
-				freeaddrinfo(result);
-				return -EINVAL;
-			}
-		}
+
 		if (flags & OSMO_SOCK_F_CONNECT) {
 			rc = connect(sfd, rp->ai_addr, rp->ai_addrlen);
 			if (rc != -1 || (rc == -1 && errno == EINPROGRESS))
diff --git a/tests/socket/socket_test.c b/tests/socket/socket_test.c
index 5b6abc4..57425ef 100644
--- a/tests/socket/socket_test.c
+++ b/tests/socket/socket_test.c
@@ -73,6 +73,52 @@
 	return 0;
 }
 
+static int test_sockinit2(void)
+{
+	int fd, rc;
+	char *name;
+
+	printf("Checking osmo_sock_init2() with bind to a random local UDP port\n");
+	fd = osmo_sock_init2(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+			    "0.0.0.0", 0, NULL, 0, OSMO_SOCK_F_BIND);
+	OSMO_ASSERT(fd >= 0);
+	name = osmo_sock_get_name(NULL, fd);
+	/* expect it to be not connected. We cannot match on INADDR_ANY,
+	 * as apparently that won't work on FreeBSD if there's only one
+	 * address (e.g. 127.0.0.1) assigned to the entire system, like
+	 * the Osmocom FreeBSD build slaves */
+	OSMO_ASSERT(!strncmp(name, "(NULL<->", 7));
+	talloc_free(name);
+	/* expect it to be blocking */
+	rc = fcntl(fd, F_GETFL);
+	OSMO_ASSERT(!(rc & O_NONBLOCK));
+	close(fd);
+
+	printf("Checking osmo_sock_init2() for OSMO_SOCK_F_NONBLOCK\n");
+	fd = osmo_sock_init2(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+			    "0.0.0.0", 0, NULL, 0, OSMO_SOCK_F_BIND|OSMO_SOCK_F_NONBLOCK);
+	OSMO_ASSERT(fd >= 0);
+	/* expect it to be blocking */
+	rc = fcntl(fd, F_GETFL);
+	OSMO_ASSERT(rc & O_NONBLOCK);
+	close(fd);
+
+	printf("Checking osmo_sock_init2() for invalid flags\n");
+	fd = osmo_sock_init2(AF_INET, SOCK_DGRAM, IPPROTO_UDP, "0.0.0.0", 0, NULL, 0, 0);
+	OSMO_ASSERT(fd < 0);
+
+	printf("Checking osmo_sock_init2() for combined BIND + CONNECT\n");
+	fd = osmo_sock_init2(AF_INET, SOCK_DGRAM, IPPROTO_UDP, "127.0.0.1", 0, "127.0.0.1", 53,
+			     OSMO_SOCK_F_BIND|OSMO_SOCK_F_CONNECT);
+	OSMO_ASSERT(fd >= 0);
+	name = osmo_sock_get_name(NULL, fd);
+	OSMO_ASSERT(!strncmp(name, "(127.0.0.1:53<->127.0.0.1", 25));
+	talloc_free(name);
+
+	return 0;
+}
+
+
 const struct log_info_cat default_categories[] = {
 };
 
@@ -88,6 +134,7 @@
 	log_set_print_filename(osmo_stderr_target, 0);
 
 	test_sockinit();
+	test_sockinit2();
 
 	return EXIT_SUCCESS;
 }
diff --git a/tests/socket/socket_test.err b/tests/socket/socket_test.err
index 5367239..ed6e186 100644
--- a/tests/socket/socket_test.err
+++ b/tests/socket/socket_test.err
@@ -1 +1,2 @@
 invalid: both bind and connect flags set: 0.0.0.0:0
+invalid: you have to specify either BIND or CONNECT flags
diff --git a/tests/socket/socket_test.ok b/tests/socket/socket_test.ok
index d6ec40e..4b24fbc 100644
--- a/tests/socket/socket_test.ok
+++ b/tests/socket/socket_test.ok
@@ -1,3 +1,7 @@
 Checking osmo_sock_init() with bind to a random local UDP port
 Checking for OSMO_SOCK_F_NONBLOCK
 Checking for invalid flags
+Checking osmo_sock_init2() with bind to a random local UDP port
+Checking osmo_sock_init2() for OSMO_SOCK_F_NONBLOCK
+Checking osmo_sock_init2() for invalid flags
+Checking osmo_sock_init2() for combined BIND + CONNECT

-- 
To view, visit https://gerrit.osmocom.org/2250
To unsubscribe, visit https://gerrit.osmocom.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Idab124bcca47872f55311a82d6818aed590965e6
Gerrit-PatchSet: 1
Gerrit-Project: libosmocore
Gerrit-Branch: master
Gerrit-Owner: Harald Welte <laforge at gnumonks.org>



More information about the gerrit-log mailing list