Add initial meta-stx to support StarlingX build
[pti/rtp.git] / meta-stx / recipes-security / gssproxy / files / client-Switch-to-non-blocking-sockets.patch
diff --git a/meta-stx/recipes-security/gssproxy/files/client-Switch-to-non-blocking-sockets.patch b/meta-stx/recipes-security/gssproxy/files/client-Switch-to-non-blocking-sockets.patch
new file mode 100644 (file)
index 0000000..2d0b34b
--- /dev/null
@@ -0,0 +1,485 @@
+From 1962e6128a4d86a7c54977577e1e4224cadbb5f7 Mon Sep 17 00:00:00 2001
+From: Alexander Scheel <ascheel@redhat.com>
+Date: Wed, 2 Aug 2017 15:11:49 -0400
+Subject: [PATCH] [client] Switch to non-blocking sockets
+
+Switch the gssproxy client library to non-blocking sockets, allowing
+for timeout and retry operations.  The client will automatically retry
+both send() and recv() operations three times on ETIMEDOUT.  If the
+combined send() and recv() hit the three time limit, ETIMEDOUT will be
+exposed to the caller in the minor status.
+
+Signed-off-by: Alexander Scheel <ascheel@redhat.com>
+Reviewed-by: Simo Sorce <simo@redhat.com>
+[rharwood@redhat.com: commit message cleanups, rebased]
+Reviewed-by: Robbie Harwood <rharwood@redhat.com>
+(cherry picked from commit d035646c8feb0b78f0c157580ca02c46cd00dd7e)
+---
+ proxy/src/client/gpm_common.c | 317 +++++++++++++++++++++++++++++++---
+ 1 file changed, 295 insertions(+), 22 deletions(-)
+
+diff --git a/proxy/src/client/gpm_common.c b/proxy/src/client/gpm_common.c
+index 2133618..dba23a6 100644
+--- a/proxy/src/client/gpm_common.c
++++ b/proxy/src/client/gpm_common.c
+@@ -7,9 +7,15 @@
+ #include <stdlib.h>
+ #include <time.h>
+ #include <pthread.h>
++#include <sys/epoll.h>
++#include <fcntl.h>
++#include <sys/timerfd.h>
+ #define FRAGMENT_BIT (1 << 31)
++#define RESPONSE_TIMEOUT 15
++#define MAX_TIMEOUT_RETRY 3
++
+ struct gpm_ctx {
+     pthread_mutex_t lock;
+     int fd;
+@@ -20,6 +26,9 @@ struct gpm_ctx {
+     gid_t gid;
+     int next_xid;
++
++    int epollfd;
++    int timerfd;
+ };
+ /* a single global struct is not particularly efficient,
+@@ -39,6 +48,8 @@ static void gpm_init_once(void)
+     pthread_mutex_init(&gpm_global_ctx.lock, &attr);
+     gpm_global_ctx.fd = -1;
++    gpm_global_ctx.epollfd = -1;
++    gpm_global_ctx.timerfd = -1;
+     seedp = time(NULL) + getpid() + pthread_self();
+     gpm_global_ctx.next_xid = rand_r(&seedp);
+@@ -69,6 +80,7 @@ static int gpm_open_socket(struct gpm_ctx *gpmctx)
+     struct sockaddr_un addr = {0};
+     char name[PATH_MAX];
+     int ret;
++    unsigned flags;
+     int fd = -1;
+     ret = get_pipe_name(name);
+@@ -86,6 +98,18 @@ static int gpm_open_socket(struct gpm_ctx *gpmctx)
+         goto done;
+     }
++    ret = fcntl(fd, F_GETFD, &flags);
++    if (ret != 0) {
++        ret = errno;
++        goto done;
++    }
++
++    ret = fcntl(fd, F_SETFD, flags | O_NONBLOCK);
++    if (ret != 0) {
++        ret = errno;
++        goto done;
++    }
++
+     ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+     if (ret == -1) {
+         ret = errno;
+@@ -163,6 +187,158 @@ static int gpm_release_sock(struct gpm_ctx *gpmctx)
+     return pthread_mutex_unlock(&gpmctx->lock);
+ }
++static void gpm_timer_close(struct gpm_ctx *gpmctx) {
++    if (gpmctx->timerfd < 0) {
++        return;
++    }
++
++    close(gpmctx->timerfd);
++    gpmctx->timerfd = -1;
++}
++
++static int gpm_timer_setup(struct gpm_ctx *gpmctx, int timeout_seconds) {
++    int ret;
++    struct itimerspec its;
++
++    if (gpmctx->timerfd >= 0) {
++        gpm_timer_close(gpmctx);
++    }
++
++    gpmctx->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
++    if (gpmctx->timerfd < 0) {
++        return errno;
++    }
++
++    its.it_interval.tv_sec = timeout_seconds;
++    its.it_interval.tv_nsec = 0;
++    its.it_value.tv_sec = timeout_seconds;
++    its.it_value.tv_nsec = 0;
++
++    ret = timerfd_settime(gpmctx->timerfd, 0, &its, NULL);
++    if (ret) {
++        ret = errno;
++        gpm_timer_close(gpmctx);
++        return ret;
++    }
++
++    return 0;
++}
++
++static void gpm_epoll_close(struct gpm_ctx *gpmctx) {
++    if (gpmctx->epollfd < 0) {
++        return;
++    }
++
++    close(gpmctx->epollfd);
++    gpmctx->epollfd = -1;
++}
++
++static int gpm_epoll_setup(struct gpm_ctx *gpmctx) {
++    struct epoll_event ev;
++    int ret;
++
++    if (gpmctx->epollfd >= 0) {
++        gpm_epoll_close(gpmctx);
++    }
++
++    gpmctx->epollfd = epoll_create1(EPOLL_CLOEXEC);
++    if (gpmctx->epollfd == -1) {
++        return errno;
++    }
++
++    /* Add timer */
++    ev.events = EPOLLIN;
++    ev.data.fd = gpmctx->timerfd;
++    ret = epoll_ctl(gpmctx->epollfd, EPOLL_CTL_ADD, gpmctx->timerfd, &ev);
++    if (ret == -1) {
++        ret = errno;
++        gpm_epoll_close(gpmctx);
++        return ret;
++    }
++
++    return ret;
++}
++
++static int gpm_epoll_wait(struct gpm_ctx *gpmctx, uint32_t event_flags) {
++    int ret;
++    int epoll_ret;
++    struct epoll_event ev;
++    struct epoll_event events[2];
++    uint64_t timer_read;
++
++    if (gpmctx->epollfd < 0) {
++        ret = gpm_epoll_setup(gpmctx);
++        if (ret)
++            return ret;
++    }
++
++    ev.events = event_flags;
++    ev.data.fd = gpmctx->fd;
++    epoll_ret = epoll_ctl(gpmctx->epollfd, EPOLL_CTL_ADD, gpmctx->fd, &ev);
++    if (epoll_ret == -1) {
++        ret = errno;
++        gpm_epoll_close(gpmctx);
++        return ret;
++    }
++
++    do {
++        epoll_ret = epoll_wait(gpmctx->epollfd, events, 2, -1);
++    } while (epoll_ret < 0 && errno == EINTR);
++
++    if (epoll_ret < 0) {
++        /* Error while waiting that isn't EINTR */
++        ret = errno;
++        gpm_epoll_close(gpmctx);
++    } else if (epoll_ret == 0) {
++        /* Shouldn't happen as timeout == -1; treat it like a timeout
++         * occurred. */
++        ret = ETIMEDOUT;
++        gpm_epoll_close(gpmctx);
++    } else if (epoll_ret == 1 && events[0].data.fd == gpmctx->timerfd) {
++        /* Got an event which is only our timer */
++        ret = read(gpmctx->timerfd, &timer_read, sizeof(uint64_t));
++        if (ret == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
++            /* In the case when reading from the timer failed, don't hide the
++             * timer error behind ETIMEDOUT such that it isn't retried */
++            ret = errno;
++        } else {
++            /* If ret == 0, then we definitely timed out. Else, if ret == -1
++             * and errno == EAGAIN or errno == EWOULDBLOCK, we're in a weird
++             * edge case where epoll thinks the timer can be read, but it
++             * is blocking more; treat it like a TIMEOUT and retry, as
++             * nothing around us would handle EAGAIN from timer and retry
++             * it. */
++            ret = ETIMEDOUT;
++        }
++        gpm_epoll_close(gpmctx);
++    } else {
++        /* If ret == 2, then we ignore the timerfd; that way if the next
++         * operation cannot be performed immediately, we timeout and retry.
++         * If ret == 1 and data.fd == gpmctx->fd, return 0. */
++        ret = 0;
++    }
++
++    epoll_ret = epoll_ctl(gpmctx->epollfd, EPOLL_CTL_DEL, gpmctx->fd, NULL);
++    if (epoll_ret == -1) {
++        /* If we previously had an error, expose that error instead of
++         * clobbering it with errno; else if no error, then assume it is
++         * better to notify of the error deleting the event than it is
++         * to continue. */
++        if (ret == 0)
++            ret = errno;
++        gpm_epoll_close(gpmctx);
++    }
++
++    return ret;
++}
++
++static int gpm_retry_socket(struct gpm_ctx *gpmctx)
++{
++    gpm_epoll_close(gpmctx);
++    gpm_close_socket(gpmctx);
++    return gpm_open_socket(gpmctx);
++}
++
+ /* must be called after the lock has been grabbed */
+ static int gpm_send_buffer(struct gpm_ctx *gpmctx,
+                            char *buffer, uint32_t length)
+@@ -183,8 +359,13 @@ static int gpm_send_buffer(struct gpm_ctx *gpmctx,
+     retry = false;
+     do {
+         do {
++            ret = gpm_epoll_wait(gpmctx, EPOLLOUT);
++            if (ret != 0) {
++                goto done;
++            }
++
+             ret = 0;
+-            wn = send(gpmctx->fd, &size, sizeof(uint32_t), MSG_NOSIGNAL);
++            wn = write(gpmctx->fd, &size, sizeof(uint32_t));
+             if (wn == -1) {
+                 ret = errno;
+             }
+@@ -192,8 +373,7 @@ static int gpm_send_buffer(struct gpm_ctx *gpmctx,
+         if (wn != 4) {
+             /* reopen and retry once */
+             if (retry == false) {
+-                gpm_close_socket(gpmctx);
+-                ret = gpm_open_socket(gpmctx);
++                ret = gpm_retry_socket(gpmctx);
+                 if (ret == 0) {
+                     retry = true;
+                     continue;
+@@ -208,9 +388,14 @@ static int gpm_send_buffer(struct gpm_ctx *gpmctx,
+     pos = 0;
+     while (length > pos) {
+-        wn = send(gpmctx->fd, buffer + pos, length - pos, MSG_NOSIGNAL);
++        ret = gpm_epoll_wait(gpmctx, EPOLLOUT);
++        if (ret) {
++            goto done;
++        }
++
++        wn = write(gpmctx->fd, buffer + pos, length - pos);
+         if (wn == -1) {
+-            if (errno == EINTR) {
++            if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
+                 continue;
+             }
+             ret = errno;
+@@ -231,7 +416,7 @@ done:
+ /* must be called after the lock has been grabbed */
+ static int gpm_recv_buffer(struct gpm_ctx *gpmctx,
+-                           char *buffer, uint32_t *length)
++                           char **buffer, uint32_t *length)
+ {
+     uint32_t size;
+     ssize_t rn;
+@@ -239,6 +424,11 @@ static int gpm_recv_buffer(struct gpm_ctx *gpmctx,
+     int ret;
+     do {
++        ret = gpm_epoll_wait(gpmctx, EPOLLIN);
++        if (ret) {
++            goto done;
++        }
++
+         ret = 0;
+         rn = read(gpmctx->fd, &size, sizeof(uint32_t));
+         if (rn == -1) {
+@@ -258,11 +448,22 @@ static int gpm_recv_buffer(struct gpm_ctx *gpmctx,
+         goto done;
+     }
++    *buffer = malloc(*length);
++    if (*buffer == NULL) {
++        ret = ENOMEM;
++        goto done;
++    }
++
+     pos = 0;
+     while (*length > pos) {
+-        rn = read(gpmctx->fd, buffer + pos, *length - pos);
++        ret = gpm_epoll_wait(gpmctx, EPOLLIN);
++        if (ret) {
++            goto done;
++        }
++
++        rn = read(gpmctx->fd, *buffer + pos, *length - pos);
+         if (rn == -1) {
+-            if (errno == EINTR) {
++            if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
+                 continue;
+             }
+             ret = errno;
+@@ -281,6 +482,7 @@ done:
+     if (ret) {
+         /* on errors we can only close the fd and return */
+         gpm_close_socket(gpmctx);
++        gpm_epoll_close(gpmctx);
+     }
+     return ret;
+ }
+@@ -309,6 +511,63 @@ static struct gpm_ctx *gpm_get_ctx(void)
+     return &gpm_global_ctx;
+ }
++static int gpm_send_recv_loop(struct gpm_ctx *gpmctx, char *send_buffer,
++                              uint32_t send_length, char** recv_buffer,
++                              uint32_t *recv_length)
++{
++    int ret;
++    int retry_count;
++
++    /* setup timer */
++    ret = gpm_timer_setup(gpmctx, RESPONSE_TIMEOUT);
++    if (ret)
++        return ret;
++
++    for (retry_count = 0; retry_count < MAX_TIMEOUT_RETRY; retry_count++) {
++        /* send to proxy */
++        ret = gpm_send_buffer(gpmctx, send_buffer, send_length);
++
++        if (ret == 0) {
++            /* No error, continue to recv */
++        } else if (ret == ETIMEDOUT) {
++            /* Close and reopen socket before trying again */
++            ret = gpm_retry_socket(gpmctx);
++            if (ret != 0)
++                return ret;
++            ret = ETIMEDOUT;
++
++            /* RETRY entire send */
++            continue;
++        } else {
++            /* Other error */
++            return ret;
++        }
++
++        /* receive answer */
++        ret = gpm_recv_buffer(gpmctx, recv_buffer, recv_length);
++        if (ret == 0) {
++            /* No error */
++            break;
++        } else if (ret == ETIMEDOUT) {
++            /* Close and reopen socket before trying again */
++            ret = gpm_retry_socket(gpmctx);
++
++            /* Free buffer and set it to NULL to prevent free(xdr_reply_ctx) */
++            free(recv_buffer);
++            recv_buffer = NULL;
++
++            if (ret != 0)
++                return ret;
++            ret = ETIMEDOUT;
++        } else {
++            /* Other error */
++            return ret;
++        }
++    }
++
++    return ret;
++}
++
+ OM_uint32 gpm_release_buffer(OM_uint32 *minor_status,
+                              gss_buffer_t buffer)
+ {
+@@ -399,15 +658,20 @@ int gpm_make_call(int proc, union gp_rpc_arg *arg, union gp_rpc_res *res)
+     gp_rpc_msg msg;
+     XDR xdr_call_ctx;
+     XDR xdr_reply_ctx;
+-    char buffer[MAX_RPC_SIZE];
+-    uint32_t length;
++    char *send_buffer = NULL;
++    char *recv_buffer = NULL;
++    uint32_t send_length;
++    uint32_t recv_length;
+     uint32_t xid;
+     bool xdrok;
+     bool sockgrab = false;
+     int ret;
+-    xdrmem_create(&xdr_call_ctx, buffer, MAX_RPC_SIZE, XDR_ENCODE);
+-    xdrmem_create(&xdr_reply_ctx, buffer, MAX_RPC_SIZE, XDR_DECODE);
++    send_buffer = malloc(MAX_RPC_SIZE);
++    if (send_buffer == NULL)
++        return ENOMEM;
++
++    xdrmem_create(&xdr_call_ctx, send_buffer, MAX_RPC_SIZE, XDR_ENCODE);
+     memset(&msg, 0, sizeof(gp_rpc_msg));
+     msg.header.type = GP_RPC_CALL;
+@@ -450,22 +714,22 @@ int gpm_make_call(int proc, union gp_rpc_arg *arg, union gp_rpc_res *res)
+         goto done;
+     }
+-    /* send to proxy */
+-    ret = gpm_send_buffer(gpmctx, buffer, xdr_getpos(&xdr_call_ctx));
+-    if (ret) {
+-        goto done;
+-    }
++    /* set send_length */
++    send_length = xdr_getpos(&xdr_call_ctx);
+-    /* receive answer */
+-    ret = gpm_recv_buffer(gpmctx, buffer, &length);
+-    if (ret) {
++    /* Send request, receive response with timeout */
++    ret = gpm_send_recv_loop(gpmctx, send_buffer, send_length, &recv_buffer,
++                             &recv_length);
++    if (ret)
+         goto done;
+-    }
+     /* release the lock */
+     gpm_release_sock(gpmctx);
+     sockgrab = false;
++    /* Create the reply context */
++    xdrmem_create(&xdr_reply_ctx, recv_buffer, recv_length, XDR_DECODE);
++
+     /* decode header */
+     memset(&msg, 0, sizeof(gp_rpc_msg));
+     xdrok = xdr_gp_rpc_msg(&xdr_reply_ctx, &msg);
+@@ -489,12 +753,21 @@ int gpm_make_call(int proc, union gp_rpc_arg *arg, union gp_rpc_res *res)
+     }
+ done:
++    gpm_timer_close(gpmctx);
++    gpm_epoll_close(gpmctx);
++
+     if (sockgrab) {
+         gpm_release_sock(gpmctx);
+     }
+     xdr_free((xdrproc_t)xdr_gp_rpc_msg, (char *)&msg);
+     xdr_destroy(&xdr_call_ctx);
+-    xdr_destroy(&xdr_reply_ctx);
++
++    if (recv_buffer != NULL)
++        xdr_destroy(&xdr_reply_ctx);
++
++    free(send_buffer);
++    free(recv_buffer);
++
+     return ret;
+ }