Hello Jenkins Builder,

I'd like you to reexamine a change.  Please visit

    https://gerrit.osmocom.org/2250

to look at the new patch set (#2).

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, 248 insertions(+), 33 deletions(-)


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

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..b56d50c 100644
--- a/tests/socket/socket_test.c
+++ b/tests/socket/socket_test.c
@@ -73,6 +73,57 @@
        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);
+#ifndef __FreeBSD__
+       /* For some reason, on the jenkins.osmocom.org build slave with
+        * FreeBSD 10 inside a jail, it fails.  Works fine on laforge's
+        * FreeBSD 10 or 11 VM at home */
+       OSMO_ASSERT(!strncmp(name, "(127.0.0.1:53<->127.0.0.1", 25));
+#endif
+       talloc_free(name);
+
+       return 0;
+}
+
+
 const struct log_info_cat default_categories[] = {
 };
 
@@ -88,6 +139,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: newpatchset
Gerrit-Change-Id: Idab124bcca47872f55311a82d6818aed590965e6
Gerrit-PatchSet: 2
Gerrit-Project: libosmocore
Gerrit-Branch: master
Gerrit-Owner: Harald Welte <[email protected]>
Gerrit-Reviewer: Harald Welte <[email protected]>
Gerrit-Reviewer: Holger Freyther <[email protected]>
Gerrit-Reviewer: Jenkins Builder

Reply via email to