Move Autoconf's macro definitions into config.h.
[sliver-openvswitch.git] / lib / vconn-tcp.c
index f97761f..7fbec08 100644 (file)
@@ -1,27 +1,41 @@
-/* Copyright (C) 2007 Board of Trustees, Leland Stanford Jr. University.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ * 
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * 
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
  */
 
+#include <config.h>
 #include "vconn.h"
 #include <assert.h>
 #include <errno.h>
+#include <inttypes.h>
 #include <netdb.h>
 #include <poll.h>
 #include <sys/types.h>
@@ -35,6 +49,8 @@
 #include "util.h"
 #include "openflow.h"
 #include "ofp-print.h"
+#include "packets.h"
+#include "poll-loop.h"
 
 #include "vlog.h"
 #define THIS_MODULE VLM_vconn_tcp
@@ -47,22 +63,17 @@ struct tcp_vconn
     int fd;
     struct buffer *rxbuf;
     struct buffer *txbuf;
+    struct poll_waiter *tx_waiter;
 };
 
 static int
-new_tcp_vconn(const char *name, int fd, struct vconn **vconnp) 
+new_tcp_vconn(const char *name, int fd, int connect_status,
+              const struct sockaddr_in *sin, struct vconn **vconnp)
 {
     struct tcp_vconn *tcp;
     int on = 1;
     int retval;
 
-    retval = set_nonblocking(fd);
-    if (retval) {
-        VLOG_ERR("%s: set_nonblocking: %s", name, strerror(retval));
-        close(fd);
-        return retval;
-    }
-
     retval = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof on);
     if (retval) {
         VLOG_ERR("%s: setsockopt(TCP_NODELAY): %s", name, strerror(errno));
@@ -72,18 +83,21 @@ new_tcp_vconn(const char *name, int fd, struct vconn **vconnp)
 
     tcp = xmalloc(sizeof *tcp);
     tcp->vconn.class = &tcp_vconn_class;
+    tcp->vconn.connect_status = connect_status;
+    tcp->vconn.ip = sin->sin_addr.s_addr;
     tcp->fd = fd;
     tcp->txbuf = NULL;
+    tcp->tx_waiter = NULL;
     tcp->rxbuf = NULL;
     *vconnp = &tcp->vconn;
     return 0;
 }
 
 static struct tcp_vconn *
-tcp_vconn_cast(struct vconn *vconn) 
+tcp_vconn_cast(struct vconn *vconn)
 {
     assert(vconn->class == &tcp_vconn_class);
-    return CONTAINER_OF(vconn, struct tcp_vconn, vconn); 
+    return CONTAINER_OF(vconn, struct tcp_vconn, vconn);
 }
 
 
@@ -104,7 +118,8 @@ tcp_open(const char *name, char *suffix, struct vconn **vconnp)
     host_name = strtok_r(suffix, "::", &save_ptr);
     port_string = strtok_r(NULL, "::", &save_ptr);
     if (!host_name) {
-        fatal(0, "%s: bad peer name format", name);
+        error(0, "%s: bad peer name format", name);
+        return EAFNOSUPPORT;
     }
 
     memset(&sin, 0, sizeof sin);
@@ -120,61 +135,41 @@ tcp_open(const char *name, char *suffix, struct vconn **vconnp)
         return errno;
     }
 
-    retval = connect(fd, (struct sockaddr *) &sin, sizeof sin);
-    if (retval < 0) {
-        int error = errno;
-        VLOG_ERR("%s: connect: %s", name, strerror(error));
+    retval = set_nonblocking(fd);
+    if (retval) {
         close(fd);
-        return error;
+        return retval;
     }
 
-    return new_tcp_vconn(name, fd, vconnp);
+    retval = connect(fd, (struct sockaddr *) &sin, sizeof sin);
+    if (retval < 0) {
+        if (errno == EINPROGRESS) {
+            return new_tcp_vconn(name, fd, EAGAIN, &sin, vconnp);
+        } else {
+            int error = errno;
+            VLOG_ERR("%s: connect: %s", name, strerror(error));
+            close(fd);
+            return error;
+        }
+    } else {
+        return new_tcp_vconn(name, fd, 0, &sin, vconnp);
+    }
 }
 
 static void
-tcp_close(struct vconn *vconn) 
+tcp_close(struct vconn *vconn)
 {
     struct tcp_vconn *tcp = tcp_vconn_cast(vconn);
+    poll_cancel(tcp->tx_waiter);
     close(tcp->fd);
     free(tcp);
 }
 
-static bool
-tcp_prepoll(struct vconn *vconn, int want, struct pollfd *pfd) 
-{
-    struct tcp_vconn *tcp = tcp_vconn_cast(vconn);
-    pfd->fd = tcp->fd;
-    if (want & WANT_RECV) {
-        pfd->events |= POLLIN;
-    }
-    if (want & WANT_SEND || tcp->txbuf) {
-        pfd->events |= POLLOUT;
-    }
-    return false;
-}
-
-static void
-tcp_postpoll(struct vconn *vconn, short int *revents)
+static int
+tcp_connect(struct vconn *vconn)
 {
     struct tcp_vconn *tcp = tcp_vconn_cast(vconn);
-    if (*revents & POLLOUT && tcp->txbuf) {
-        ssize_t n = write(tcp->fd, tcp->txbuf->data, tcp->txbuf->size);
-        if (n < 0) {
-            if (errno != EAGAIN) {
-                VLOG_ERR("send: %s", strerror(errno));
-                *revents |= POLLERR;
-            }
-        } else if (n > 0) {
-            buffer_pull(tcp->txbuf, n);
-            if (tcp->txbuf->size == 0) {
-                buffer_delete(tcp->txbuf);
-                tcp->txbuf = NULL;
-            }
-        }
-        if (tcp->txbuf) {
-            *revents &= ~POLLOUT;
-        }
-    }
+    return check_connection_completion(tcp->fd);
 }
 
 static int
@@ -201,8 +196,13 @@ again:
             return EPROTO;
         }
         want_bytes = length - rx->size;
+        if (!want_bytes) {
+            *bufferp = rx;
+            tcp->rxbuf = NULL;
+            return 0;
+        }
     }
-    buffer_reserve_tailroom(rx, want_bytes);
+    buffer_prealloc_tailroom(rx, want_bytes);
 
     retval = read(tcp->fd, buffer_tail(rx), want_bytes);
     if (retval > 0) {
@@ -218,14 +218,49 @@ again:
         }
         return EAGAIN;
     } else if (retval == 0) {
-        return rx->size ? EPROTO : EOF;
+        if (rx->size) {
+            VLOG_ERR("connection dropped mid-packet");
+            return EPROTO;
+        } else {
+            return EOF; 
+        }
     } else {
         return retval ? errno : EAGAIN;
     }
 }
 
+static void
+tcp_clear_txbuf(struct tcp_vconn *tcp)
+{
+    buffer_delete(tcp->txbuf);
+    tcp->txbuf = NULL;
+    tcp->tx_waiter = NULL;
+}
+
+static void
+tcp_do_tx(int fd UNUSED, short int revents UNUSED, void *vconn_)
+{
+    struct vconn *vconn = vconn_;
+    struct tcp_vconn *tcp = tcp_vconn_cast(vconn);
+    ssize_t n = write(tcp->fd, tcp->txbuf->data, tcp->txbuf->size);
+    if (n < 0) {
+        if (errno != EAGAIN) {
+            VLOG_ERR("send: %s", strerror(errno));
+            tcp_clear_txbuf(tcp);
+            return;
+        }
+    } else if (n > 0) {
+        buffer_pull(tcp->txbuf, n);
+        if (!tcp->txbuf->size) {
+            tcp_clear_txbuf(tcp);
+            return;
+        }
+    }
+    tcp->tx_waiter = poll_fd_callback(tcp->fd, POLLOUT, tcp_do_tx, vconn);
+}
+
 static int
-tcp_send(struct vconn *vconn, struct buffer *buffer) 
+tcp_send(struct vconn *vconn, struct buffer *buffer)
 {
     struct tcp_vconn *tcp = tcp_vconn_cast(vconn);
     ssize_t retval;
@@ -243,20 +278,47 @@ tcp_send(struct vconn *vconn, struct buffer *buffer)
         if (retval > 0) {
             buffer_pull(buffer, retval);
         }
+        tcp->tx_waiter = poll_fd_callback(tcp->fd, POLLOUT, tcp_do_tx, vconn);
         return 0;
     } else {
         return errno;
     }
 }
 
+static void
+tcp_wait(struct vconn *vconn, enum vconn_wait_type wait)
+{
+    struct tcp_vconn *tcp = tcp_vconn_cast(vconn);
+    switch (wait) {
+    case WAIT_CONNECT:
+        poll_fd_wait(tcp->fd, POLLOUT);
+        break;
+
+    case WAIT_SEND:
+        if (!tcp->txbuf) {
+            poll_fd_wait(tcp->fd, POLLOUT);
+        } else {
+            /* Nothing to do: need to drain txbuf first. */
+        }
+        break;
+
+    case WAIT_RECV:
+        poll_fd_wait(tcp->fd, POLLIN);
+        break;
+
+    default:
+        NOT_REACHED();
+    }
+}
+
 struct vconn_class tcp_vconn_class = {
     .name = "tcp",
     .open = tcp_open,
     .close = tcp_close,
-    .prepoll = tcp_prepoll,
-    .postpoll = tcp_postpoll,
+    .connect = tcp_connect,
     .recv = tcp_recv,
     .send = tcp_send,
+    .wait = tcp_wait,
 };
 \f
 /* Passive TCP. */
@@ -268,10 +330,10 @@ struct ptcp_vconn
 };
 
 static struct ptcp_vconn *
-ptcp_vconn_cast(struct vconn *vconn) 
+ptcp_vconn_cast(struct vconn *vconn)
 {
     assert(vconn->class == &ptcp_vconn_class);
-    return CONTAINER_OF(vconn, struct ptcp_vconn, vconn); 
+    return CONTAINER_OF(vconn, struct ptcp_vconn, vconn);
 }
 
 static int
@@ -317,56 +379,71 @@ ptcp_open(const char *name, char *suffix, struct vconn **vconnp)
 
     retval = set_nonblocking(fd);
     if (retval) {
-        VLOG_ERR("%s: set_nonblocking: %s", name, strerror(retval));
         close(fd);
         return retval;
     }
 
     ptcp = xmalloc(sizeof *ptcp);
     ptcp->vconn.class = &ptcp_vconn_class;
+    ptcp->vconn.connect_status = 0;
     ptcp->fd = fd;
     *vconnp = &ptcp->vconn;
     return 0;
 }
 
 static void
-ptcp_close(struct vconn *vconn) 
+ptcp_close(struct vconn *vconn)
 {
     struct ptcp_vconn *ptcp = ptcp_vconn_cast(vconn);
     close(ptcp->fd);
     free(ptcp);
 }
 
-static bool
-ptcp_prepoll(struct vconn *vconn, int want, struct pollfd *pfd) 
-{
-    struct ptcp_vconn *ptcp = ptcp_vconn_cast(vconn);
-    pfd->fd = ptcp->fd;
-    if (want & WANT_ACCEPT) {
-        pfd->events |= POLLIN;
-    }
-    return false;
-}
-
 static int
-ptcp_accept(struct vconn *vconn, struct vconn **new_vconnp) 
+ptcp_accept(struct vconn *vconn, struct vconn **new_vconnp)
 {
     struct ptcp_vconn *ptcp = ptcp_vconn_cast(vconn);
+    struct sockaddr_in sin;
+    socklen_t sin_len = sizeof sin;
+    char name[128];
     int new_fd;
+    int error;
 
-    new_fd = accept(ptcp->fd, NULL, NULL);
+    new_fd = accept(ptcp->fd, &sin, &sin_len);
     if (new_fd < 0) {
-        return errno;
+        int error = errno;
+        if (error != EAGAIN) {
+            VLOG_DBG("accept: %s", strerror(error));
+        }
+        return error;
     }
 
-    return new_tcp_vconn("tcp" /* FIXME */, new_fd, new_vconnp);
+    error = set_nonblocking(new_fd);
+    if (error) {
+        close(new_fd);
+        return error;
+    }
+
+    sprintf(name, "tcp:"IP_FMT, IP_ARGS(&sin.sin_addr));
+    if (sin.sin_port != htons(OFP_TCP_PORT)) {
+        sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin.sin_port));
+    }
+    return new_tcp_vconn(name, new_fd, 0, &sin, new_vconnp);
+}
+
+static void
+ptcp_wait(struct vconn *vconn, enum vconn_wait_type wait)
+{
+    struct ptcp_vconn *ptcp = ptcp_vconn_cast(vconn);
+    assert(wait == WAIT_ACCEPT);
+    poll_fd_wait(ptcp->fd, POLLIN);
 }
 
 struct vconn_class ptcp_vconn_class = {
     .name = "ptcp",
     .open = ptcp_open,
     .close = ptcp_close,
-    .prepoll = ptcp_prepoll,
     .accept = ptcp_accept,
+    .wait = ptcp_wait
 };