sync with latest
[sdk/emulator/qemu.git] / net / socket.c
index 3182b37..c172c24 100644 (file)
@@ -26,6 +26,7 @@
 #include "config-host.h"
 
 #include "net.h"
+#include "monitor.h"
 #include "qemu-char.h"
 #include "qemu-common.h"
 #include "qemu-error.h"
@@ -33,7 +34,8 @@
 #include "qemu_socket.h"
 
 typedef struct NetSocketState {
-    VLANClientState nc;
+    NetClientState nc;
+    int listen_fd;
     int fd;
     int state; /* 0 = getting length, 1 = getting data */
     unsigned int index;
@@ -42,15 +44,10 @@ typedef struct NetSocketState {
     struct sockaddr_in dgram_dst; /* contains inet host and port destination iff connectionless (SOCK_DGRAM) */
 } NetSocketState;
 
-typedef struct NetSocketListenState {
-    VLANState *vlan;
-    char *model;
-    char *name;
-    int fd;
-} NetSocketListenState;
+static void net_socket_accept(void *opaque);
 
 /* XXX: we consider we can send the whole packet without blocking */
-static ssize_t net_socket_receive(VLANClientState *nc, const uint8_t *buf, size_t size)
+static ssize_t net_socket_receive(NetClientState *nc, const uint8_t *buf, size_t size)
 {
     NetSocketState *s = DO_UPCAST(NetSocketState, nc, nc);
     uint32_t len;
@@ -60,7 +57,7 @@ static ssize_t net_socket_receive(VLANClientState *nc, const uint8_t *buf, size_
     return send_all(s->fd, buf, size);
 }
 
-static ssize_t net_socket_receive_dgram(VLANClientState *nc, const uint8_t *buf, size_t size)
+static ssize_t net_socket_receive_dgram(NetClientState *nc, const uint8_t *buf, size_t size)
 {
     NetSocketState *s = DO_UPCAST(NetSocketState, nc, nc);
 
@@ -76,7 +73,7 @@ static void net_socket_send(void *opaque)
     uint8_t buf1[4096];
     const uint8_t *buf;
 
-    size = recv(s->fd, (void *)buf1, sizeof(buf1), 0);
+    size = qemu_recv(s->fd, buf1, sizeof(buf1), 0);
     if (size < 0) {
         err = socket_error();
         if (err != EWOULDBLOCK)
@@ -85,7 +82,19 @@ static void net_socket_send(void *opaque)
         /* end of connection */
     eoc:
         qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
+        if (s->listen_fd != -1) {
+            qemu_set_fd_handler(s->listen_fd, net_socket_accept, NULL, s);
+        }
         closesocket(s->fd);
+
+        s->fd = -1;
+        s->state = 0;
+        s->index = 0;
+        s->packet_len = 0;
+        s->nc.link_down = true;
+        memset(s->buf, 0, sizeof(s->buf));
+        memset(s->nc.info_str, 0, sizeof(s->nc.info_str));
+
         return;
     }
     buf = buf1;
@@ -138,7 +147,7 @@ static void net_socket_send_dgram(void *opaque)
     NetSocketState *s = opaque;
     int size;
 
-    size = recv(s->fd, (void *)s->buf, sizeof(s->buf), 0);
+    size = qemu_recv(s->fd, s->buf, sizeof(s->buf), 0);
     if (size < 0)
         return;
     if (size == 0) {
@@ -154,11 +163,18 @@ static int net_socket_mcast_create(struct sockaddr_in *mcastaddr, struct in_addr
     struct ip_mreq imr;
     int fd;
     int val, ret;
+#ifdef __OpenBSD__
+    unsigned char loop;
+#else
+    int loop;
+#endif
+
     if (!IN_MULTICAST(ntohl(mcastaddr->sin_addr.s_addr))) {
-       fprintf(stderr, "qemu: error: specified mcastaddr \"%s\" (0x%08x) does not contain a multicast address\n",
-               inet_ntoa(mcastaddr->sin_addr),
+        fprintf(stderr, "qemu: error: specified mcastaddr \"%s\" (0x%08x) "
+                "does not contain a multicast address\n",
+                inet_ntoa(mcastaddr->sin_addr),
                 (int)ntohl(mcastaddr->sin_addr.s_addr));
-       return -1;
+        return -1;
 
     }
     fd = qemu_socket(PF_INET, SOCK_DGRAM, 0);
@@ -171,8 +187,8 @@ static int net_socket_mcast_create(struct sockaddr_in *mcastaddr, struct in_addr
     ret=setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
                    (const char *)&val, sizeof(val));
     if (ret < 0) {
-       perror("setsockopt(SOL_SOCKET, SO_REUSEADDR)");
-       goto fail;
+        perror("setsockopt(SOL_SOCKET, SO_REUSEADDR)");
+        goto fail;
     }
 
     ret = bind(fd, (struct sockaddr *)mcastaddr, sizeof(*mcastaddr));
@@ -192,17 +208,17 @@ static int net_socket_mcast_create(struct sockaddr_in *mcastaddr, struct in_addr
     ret = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
                      (const char *)&imr, sizeof(struct ip_mreq));
     if (ret < 0) {
-       perror("setsockopt(IP_ADD_MEMBERSHIP)");
-       goto fail;
+        perror("setsockopt(IP_ADD_MEMBERSHIP)");
+        goto fail;
     }
 
     /* Force mcast msgs to loopback (eg. several QEMUs in same host */
-    val = 1;
+    loop = 1;
     ret=setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP,
-                   (const char *)&val, sizeof(val));
+                   (const char *)&loop, sizeof(loop));
     if (ret < 0) {
-       perror("setsockopt(SOL_IP, IP_MULTICAST_LOOP)");
-       goto fail;
+        perror("setsockopt(SOL_IP, IP_MULTICAST_LOOP)");
+        goto fail;
     }
 
     /* If a bind address is given, only send packets from that address */
@@ -223,21 +239,29 @@ fail:
     return -1;
 }
 
-static void net_socket_cleanup(VLANClientState *nc)
+static void net_socket_cleanup(NetClientState *nc)
 {
     NetSocketState *s = DO_UPCAST(NetSocketState, nc, nc);
-    qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
-    close(s->fd);
+    if (s->fd != -1) {
+        qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
+        close(s->fd);
+        s->fd = -1;
+    }
+    if (s->listen_fd != -1) {
+        qemu_set_fd_handler(s->listen_fd, NULL, NULL, NULL);
+        closesocket(s->listen_fd);
+        s->listen_fd = -1;
+    }
 }
 
 static NetClientInfo net_dgram_socket_info = {
-    .type = NET_CLIENT_TYPE_SOCKET,
+    .type = NET_CLIENT_OPTIONS_KIND_SOCKET,
     .size = sizeof(NetSocketState),
     .receive = net_socket_receive_dgram,
     .cleanup = net_socket_cleanup,
 };
 
-static NetSocketState *net_socket_fd_init_dgram(VLANState *vlan,
+static NetSocketState *net_socket_fd_init_dgram(NetClientState *peer,
                                                 const char *model,
                                                 const char *name,
                                                 int fd, int is_connected)
@@ -245,7 +269,7 @@ static NetSocketState *net_socket_fd_init_dgram(VLANState *vlan,
     struct sockaddr_in saddr;
     int newfd;
     socklen_t saddr_len;
-    VLANClientState *nc;
+    NetClientState *nc;
     NetSocketState *s;
 
     /* fd passed: multicast: "learn" dgram_dst address from bound address and save it
@@ -254,48 +278,55 @@ static NetSocketState *net_socket_fd_init_dgram(VLANState *vlan,
      */
 
     if (is_connected) {
-       if (getsockname(fd, (struct sockaddr *) &saddr, &saddr_len) == 0) {
-           /* must be bound */
-           if (saddr.sin_addr.s_addr==0) {
-               fprintf(stderr, "qemu: error: init_dgram: fd=%d unbound, cannot setup multicast dst addr\n",
-                       fd);
-               return NULL;
-           }
-           /* clone dgram socket */
-           newfd = net_socket_mcast_create(&saddr, NULL);
-           if (newfd < 0) {
-               /* error already reported by net_socket_mcast_create() */
-               close(fd);
-               return NULL;
-           }
-           /* clone newfd to fd, close newfd */
-           dup2(newfd, fd);
-           close(newfd);
-
-       } else {
-           fprintf(stderr, "qemu: error: init_dgram: fd=%d failed getsockname(): %s\n",
-                   fd, strerror(errno));
-           return NULL;
-       }
-    }
-
-    nc = qemu_new_net_client(&net_dgram_socket_info, vlan, NULL, model, name);
+        if (getsockname(fd, (struct sockaddr *) &saddr, &saddr_len) == 0) {
+            /* must be bound */
+            if (saddr.sin_addr.s_addr == 0) {
+                fprintf(stderr, "qemu: error: init_dgram: fd=%d unbound, "
+                        "cannot setup multicast dst addr\n", fd);
+                goto err;
+            }
+            /* clone dgram socket */
+            newfd = net_socket_mcast_create(&saddr, NULL);
+            if (newfd < 0) {
+                /* error already reported by net_socket_mcast_create() */
+                goto err;
+            }
+            /* clone newfd to fd, close newfd */
+            dup2(newfd, fd);
+            close(newfd);
+
+        } else {
+            fprintf(stderr,
+                    "qemu: error: init_dgram: fd=%d failed getsockname(): %s\n",
+                    fd, strerror(errno));
+            goto err;
+        }
+    }
+
+    nc = qemu_new_net_client(&net_dgram_socket_info, peer, model, name);
 
     snprintf(nc->info_str, sizeof(nc->info_str),
-           "socket: fd=%d (%s mcast=%s:%d)",
-           fd, is_connected ? "cloned" : "",
-           inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
+            "socket: fd=%d (%s mcast=%s:%d)",
+            fd, is_connected ? "cloned" : "",
+            inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
 
     s = DO_UPCAST(NetSocketState, nc, nc);
 
     s->fd = fd;
+    s->listen_fd = -1;
 
     qemu_set_fd_handler(s->fd, net_socket_send_dgram, NULL, s);
 
     /* mcast: save bound address as dst */
-    if (is_connected) s->dgram_dst=saddr;
+    if (is_connected) {
+        s->dgram_dst = saddr;
+    }
 
     return s;
+
+err:
+    closesocket(fd);
+    return NULL;
 }
 
 static void net_socket_connect(void *opaque)
@@ -305,27 +336,28 @@ static void net_socket_connect(void *opaque)
 }
 
 static NetClientInfo net_socket_info = {
-    .type = NET_CLIENT_TYPE_SOCKET,
+    .type = NET_CLIENT_OPTIONS_KIND_SOCKET,
     .size = sizeof(NetSocketState),
     .receive = net_socket_receive,
     .cleanup = net_socket_cleanup,
 };
 
-static NetSocketState *net_socket_fd_init_stream(VLANState *vlan,
+static NetSocketState *net_socket_fd_init_stream(NetClientState *peer,
                                                  const char *model,
                                                  const char *name,
                                                  int fd, int is_connected)
 {
-    VLANClientState *nc;
+    NetClientState *nc;
     NetSocketState *s;
 
-    nc = qemu_new_net_client(&net_socket_info, vlan, NULL, model, name);
+    nc = qemu_new_net_client(&net_socket_info, peer, model, name);
 
     snprintf(nc->info_str, sizeof(nc->info_str), "socket: fd=%d", fd);
 
     s = DO_UPCAST(NetSocketState, nc, nc);
 
     s->fd = fd;
+    s->listen_fd = -1;
 
     if (is_connected) {
         net_socket_connect(s);
@@ -335,7 +367,7 @@ static NetSocketState *net_socket_fd_init_stream(VLANState *vlan,
     return s;
 }
 
-static NetSocketState *net_socket_fd_init(VLANState *vlan,
+static NetSocketState *net_socket_fd_init(NetClientState *peer,
                                           const char *model, const char *name,
                                           int fd, int is_connected)
 {
@@ -343,63 +375,63 @@ static NetSocketState *net_socket_fd_init(VLANState *vlan,
 
     if(getsockopt(fd, SOL_SOCKET, SO_TYPE, (char *)&so_type,
         (socklen_t *)&optlen)< 0) {
-       fprintf(stderr, "qemu: error: getsockopt(SO_TYPE) for fd=%d failed\n", fd);
-       return NULL;
+        fprintf(stderr, "qemu: error: getsockopt(SO_TYPE) for fd=%d failed\n",
+                fd);
+        closesocket(fd);
+        return NULL;
     }
     switch(so_type) {
     case SOCK_DGRAM:
-        return net_socket_fd_init_dgram(vlan, model, name, fd, is_connected);
+        return net_socket_fd_init_dgram(peer, model, name, fd, is_connected);
     case SOCK_STREAM:
-        return net_socket_fd_init_stream(vlan, model, name, fd, is_connected);
+        return net_socket_fd_init_stream(peer, model, name, fd, is_connected);
     default:
         /* who knows ... this could be a eg. a pty, do warn and continue as stream */
         fprintf(stderr, "qemu: warning: socket type=%d for fd=%d is not SOCK_DGRAM or SOCK_STREAM\n", so_type, fd);
-        return net_socket_fd_init_stream(vlan, model, name, fd, is_connected);
+        return net_socket_fd_init_stream(peer, model, name, fd, is_connected);
     }
     return NULL;
 }
 
 static void net_socket_accept(void *opaque)
 {
-    NetSocketListenState *s = opaque;
-    NetSocketState *s1;
+    NetSocketState *s = opaque;
     struct sockaddr_in saddr;
     socklen_t len;
     int fd;
 
     for(;;) {
         len = sizeof(saddr);
-        fd = qemu_accept(s->fd, (struct sockaddr *)&saddr, &len);
+        fd = qemu_accept(s->listen_fd, (struct sockaddr *)&saddr, &len);
         if (fd < 0 && errno != EINTR) {
             return;
         } else if (fd >= 0) {
+            qemu_set_fd_handler(s->listen_fd, NULL, NULL, NULL);
             break;
         }
     }
-    s1 = net_socket_fd_init(s->vlan, s->model, s->name, fd, 1);
-    if (!s1) {
-        closesocket(fd);
-    } else {
-        snprintf(s1->nc.info_str, sizeof(s1->nc.info_str),
-                 "socket: connection from %s:%d",
-                 inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
-    }
+
+    s->fd = fd;
+    s->nc.link_down = false;
+    net_socket_connect(s);
+    snprintf(s->nc.info_str, sizeof(s->nc.info_str),
+             "socket: connection from %s:%d",
+             inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
 }
 
-static int net_socket_listen_init(VLANState *vlan,
+static int net_socket_listen_init(NetClientState *peer,
                                   const char *model,
                                   const char *name,
                                   const char *host_str)
 {
-    NetSocketListenState *s;
-    int fd, val, ret;
+    NetClientState *nc;
+    NetSocketState *s;
     struct sockaddr_in saddr;
+    int fd, val, ret;
 
     if (parse_host_port(&saddr, host_str) < 0)
         return -1;
 
-    s = qemu_mallocz(sizeof(NetSocketListenState));
-
     fd = qemu_socket(PF_INET, SOCK_STREAM, 0);
     if (fd < 0) {
         perror("socket");
@@ -414,22 +446,27 @@ static int net_socket_listen_init(VLANState *vlan,
     ret = bind(fd, (struct sockaddr *)&saddr, sizeof(saddr));
     if (ret < 0) {
         perror("bind");
+        closesocket(fd);
         return -1;
     }
     ret = listen(fd, 0);
     if (ret < 0) {
         perror("listen");
+        closesocket(fd);
         return -1;
     }
-    s->vlan = vlan;
-    s->model = qemu_strdup(model);
-    s->name = name ? qemu_strdup(name) : NULL;
-    s->fd = fd;
-    qemu_set_fd_handler(fd, net_socket_accept, NULL, s);
+
+    nc = qemu_new_net_client(&net_socket_info, peer, model, name);
+    s = DO_UPCAST(NetSocketState, nc, nc);
+    s->fd = -1;
+    s->listen_fd = fd;
+    s->nc.link_down = true;
+
+    qemu_set_fd_handler(s->listen_fd, net_socket_accept, NULL, s);
     return 0;
 }
 
-static int net_socket_connect_init(VLANState *vlan,
+static int net_socket_connect_init(NetClientState *peer,
                                    const char *model,
                                    const char *name,
                                    const char *host_str)
@@ -457,7 +494,7 @@ static int net_socket_connect_init(VLANState *vlan,
             } else if (err == EINPROGRESS) {
                 break;
 #ifdef _WIN32
-            } else if (err == WSAEALREADY) {
+            } else if (err == WSAEALREADY || err == WSAEINVAL) {
                 break;
 #endif
             } else {
@@ -470,7 +507,7 @@ static int net_socket_connect_init(VLANState *vlan,
             break;
         }
     }
-    s = net_socket_fd_init(vlan, model, name, fd, connected);
+    s = net_socket_fd_init(peer, model, name, fd, connected);
     if (!s)
         return -1;
     snprintf(s->nc.info_str, sizeof(s->nc.info_str),
@@ -479,7 +516,7 @@ static int net_socket_connect_init(VLANState *vlan,
     return 0;
 }
 
-static int net_socket_mcast_init(VLANState *vlan,
+static int net_socket_mcast_init(NetClientState *peer,
                                  const char *model,
                                  const char *name,
                                  const char *host_str,
@@ -503,9 +540,9 @@ static int net_socket_mcast_init(VLANState *vlan,
 
     fd = net_socket_mcast_create(&saddr, param_localaddr);
     if (fd < 0)
-       return -1;
+        return -1;
 
-    s = net_socket_fd_init(vlan, model, name, fd, 0);
+    s = net_socket_fd_init(peer, model, name, fd, 0);
     if (!s)
         return -1;
 
@@ -518,83 +555,120 @@ static int net_socket_mcast_init(VLANState *vlan,
 
 }
 
-int net_init_socket(QemuOpts *opts,
-                    Monitor *mon,
-                    const char *name,
-                    VLANState *vlan)
+static int net_socket_udp_init(NetClientState *peer,
+                                 const char *model,
+                                 const char *name,
+                                 const char *rhost,
+                                 const char *lhost)
 {
-    if (qemu_opt_get(opts, "fd")) {
-        int fd;
+    NetSocketState *s;
+    int fd, val, ret;
+    struct sockaddr_in laddr, raddr;
 
-        if (qemu_opt_get(opts, "listen") ||
-            qemu_opt_get(opts, "connect") ||
-            qemu_opt_get(opts, "mcast") ||
-            qemu_opt_get(opts, "localaddr")) {
-            error_report("listen=, connect=, mcast= and localaddr= is invalid with fd=\n");
-            return -1;
-        }
+    if (parse_host_port(&laddr, lhost) < 0) {
+        return -1;
+    }
 
-        fd = net_handle_fd_param(mon, qemu_opt_get(opts, "fd"));
-        if (fd == -1) {
-            return -1;
-        }
+    if (parse_host_port(&raddr, rhost) < 0) {
+        return -1;
+    }
 
-        if (!net_socket_fd_init(vlan, "socket", name, fd, 1)) {
-            close(fd);
-            return -1;
-        }
-    } else if (qemu_opt_get(opts, "listen")) {
-        const char *listen;
-
-        if (qemu_opt_get(opts, "fd") ||
-            qemu_opt_get(opts, "connect") ||
-            qemu_opt_get(opts, "mcast") ||
-            qemu_opt_get(opts, "localaddr")) {
-            error_report("fd=, connect=, mcast= and localaddr= is invalid with listen=\n");
-            return -1;
-        }
+    fd = qemu_socket(PF_INET, SOCK_DGRAM, 0);
+    if (fd < 0) {
+        perror("socket(PF_INET, SOCK_DGRAM)");
+        return -1;
+    }
+    val = 1;
+    ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+                   (const char *)&val, sizeof(val));
+    if (ret < 0) {
+        perror("setsockopt(SOL_SOCKET, SO_REUSEADDR)");
+        closesocket(fd);
+        return -1;
+    }
+    ret = bind(fd, (struct sockaddr *)&laddr, sizeof(laddr));
+    if (ret < 0) {
+        perror("bind");
+        closesocket(fd);
+        return -1;
+    }
 
-        listen = qemu_opt_get(opts, "listen");
+    s = net_socket_fd_init(peer, model, name, fd, 0);
+    if (!s) {
+        return -1;
+    }
 
-        if (net_socket_listen_init(vlan, "socket", name, listen) == -1) {
-            return -1;
-        }
-    } else if (qemu_opt_get(opts, "connect")) {
-        const char *connect;
-
-        if (qemu_opt_get(opts, "fd") ||
-            qemu_opt_get(opts, "listen") ||
-            qemu_opt_get(opts, "mcast") ||
-            qemu_opt_get(opts, "localaddr")) {
-            error_report("fd=, listen=, mcast= and localaddr= is invalid with connect=\n");
-            return -1;
-        }
+    s->dgram_dst = raddr;
+
+    snprintf(s->nc.info_str, sizeof(s->nc.info_str),
+             "socket: udp=%s:%d",
+             inet_ntoa(raddr.sin_addr), ntohs(raddr.sin_port));
+    return 0;
+}
+
+int net_init_socket(const NetClientOptions *opts, const char *name,
+                    NetClientState *peer)
+{
+    const NetdevSocketOptions *sock;
+
+    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_SOCKET);
+    sock = opts->socket;
 
-        connect = qemu_opt_get(opts, "connect");
+    if (sock->has_fd + sock->has_listen + sock->has_connect + sock->has_mcast +
+        sock->has_udp != 1) {
+        error_report("exactly one of fd=, listen=, connect=, mcast= or udp="
+                     " is required");
+        return -1;
+    }
+
+    if (sock->has_localaddr && !sock->has_mcast && !sock->has_udp) {
+        error_report("localaddr= is only valid with mcast= or udp=");
+        return -1;
+    }
 
-        if (net_socket_connect_init(vlan, "socket", name, connect) == -1) {
+    if (sock->has_fd) {
+        int fd;
+
+        fd = net_handle_fd_param(cur_mon, sock->fd);
+        if (fd == -1 || !net_socket_fd_init(peer, "socket", name, fd, 1)) {
             return -1;
         }
-    } else if (qemu_opt_get(opts, "mcast")) {
-        const char *mcast, *localaddr;
+        return 0;
+    }
 
-        if (qemu_opt_get(opts, "fd") ||
-            qemu_opt_get(opts, "connect") ||
-            qemu_opt_get(opts, "listen")) {
-            error_report("fd=, connect= and listen= is invalid with mcast=");
+    if (sock->has_listen) {
+        if (net_socket_listen_init(peer, "socket", name, sock->listen) == -1) {
             return -1;
         }
+        return 0;
+    }
 
-        mcast = qemu_opt_get(opts, "mcast");
-        localaddr = qemu_opt_get(opts, "localaddr");
+    if (sock->has_connect) {
+        if (net_socket_connect_init(peer, "socket", name, sock->connect) ==
+            -1) {
+            return -1;
+        }
+        return 0;
+    }
 
-        if (net_socket_mcast_init(vlan, "socket", name, mcast, localaddr) == -1) {
+    if (sock->has_mcast) {
+        /* if sock->localaddr is missing, it has been initialized to "all bits
+         * zero" */
+        if (net_socket_mcast_init(peer, "socket", name, sock->mcast,
+            sock->localaddr) == -1) {
             return -1;
         }
-    } else {
-        error_report("-socket requires fd=, listen=, connect= or mcast=");
-        return -1;
+        return 0;
     }
 
+    assert(sock->has_udp);
+    if (!sock->has_localaddr) {
+        error_report("localaddr= is mandatory with udp=");
+        return -1;
+    }
+    if (net_socket_udp_init(peer, "udp", name, sock->udp, sock->localaddr) ==
+        -1) {
+        return -1;
+    }
     return 0;
 }