diff options
Diffstat (limited to 'net')
-rw-r--r-- | net/Makefile.objs | 12 | ||||
-rw-r--r-- | net/checksum.c | 85 | ||||
-rw-r--r-- | net/checksum.h | 29 | ||||
-rw-r--r-- | net/dump.c | 185 | ||||
-rw-r--r-- | net/dump.h | 33 | ||||
-rw-r--r-- | net/hub.c | 339 | ||||
-rw-r--r-- | net/hub.h | 29 | ||||
-rw-r--r-- | net/queue.c | 256 | ||||
-rw-r--r-- | net/queue.h | 58 | ||||
-rw-r--r-- | net/slirp.c | 762 | ||||
-rw-r--r-- | net/slirp.h | 50 | ||||
-rw-r--r-- | net/socket.c | 674 | ||||
-rw-r--r-- | net/socket.h | 33 | ||||
-rw-r--r-- | net/tap-aix.c | 61 | ||||
-rw-r--r-- | net/tap-bsd.c | 147 | ||||
-rw-r--r-- | net/tap-haiku.c | 61 | ||||
-rw-r--r-- | net/tap-linux.c | 202 | ||||
-rw-r--r-- | net/tap-linux.h | 63 | ||||
-rw-r--r-- | net/tap-solaris.c | 227 | ||||
-rw-r--r-- | net/tap-win32.c | 763 | ||||
-rw-r--r-- | net/tap.c | 716 | ||||
-rw-r--r-- | net/tap.h | 64 | ||||
-rw-r--r-- | net/util.c | 60 | ||||
-rw-r--r-- | net/util.h | 32 | ||||
-rw-r--r-- | net/vde.c | 128 | ||||
-rw-r--r-- | net/vde.h | 37 |
26 files changed, 5106 insertions, 0 deletions
diff --git a/net/Makefile.objs b/net/Makefile.objs new file mode 100644 index 0000000000..cf04187717 --- /dev/null +++ b/net/Makefile.objs @@ -0,0 +1,12 @@ +common-obj-y = queue.o checksum.o util.o hub.o +common-obj-y += socket.o +common-obj-y += dump.o +common-obj-$(CONFIG_POSIX) += tap.o +common-obj-$(CONFIG_LINUX) += tap-linux.o +common-obj-$(CONFIG_WIN32) += tap-win32.o +common-obj-$(CONFIG_BSD) += tap-bsd.o +common-obj-$(CONFIG_SOLARIS) += tap-solaris.o +common-obj-$(CONFIG_AIX) += tap-aix.o +common-obj-$(CONFIG_HAIKU) += tap-haiku.o +common-obj-$(CONFIG_SLIRP) += slirp.o +common-obj-$(CONFIG_VDE) += vde.o diff --git a/net/checksum.c b/net/checksum.c new file mode 100644 index 0000000000..9919b2e5fd --- /dev/null +++ b/net/checksum.c @@ -0,0 +1,85 @@ +/* + * IP checksumming functions. + * (c) 2008 Gerd Hoffmann <kraxel@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; under version 2 or later of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "net/checksum.h" + +#define PROTO_TCP 6 +#define PROTO_UDP 17 + +uint32_t net_checksum_add(int len, uint8_t *buf) +{ + uint32_t sum = 0; + int i; + + for (i = 0; i < len; i++) { + if (i & 1) + sum += (uint32_t)buf[i]; + else + sum += (uint32_t)buf[i] << 8; + } + return sum; +} + +uint16_t net_checksum_finish(uint32_t sum) +{ + while (sum>>16) + sum = (sum & 0xFFFF)+(sum >> 16); + return ~sum; +} + +uint16_t net_checksum_tcpudp(uint16_t length, uint16_t proto, + uint8_t *addrs, uint8_t *buf) +{ + uint32_t sum = 0; + + sum += net_checksum_add(length, buf); // payload + sum += net_checksum_add(8, addrs); // src + dst address + sum += proto + length; // protocol & length + return net_checksum_finish(sum); +} + +void net_checksum_calculate(uint8_t *data, int length) +{ + int hlen, plen, proto, csum_offset; + uint16_t csum; + + if ((data[14] & 0xf0) != 0x40) + return; /* not IPv4 */ + hlen = (data[14] & 0x0f) * 4; + plen = (data[16] << 8 | data[17]) - hlen; + proto = data[23]; + + switch (proto) { + case PROTO_TCP: + csum_offset = 16; + break; + case PROTO_UDP: + csum_offset = 6; + break; + default: + return; + } + + if (plen < csum_offset+2) + return; + + data[14+hlen+csum_offset] = 0; + data[14+hlen+csum_offset+1] = 0; + csum = net_checksum_tcpudp(plen, proto, data+14+12, data+14+hlen); + data[14+hlen+csum_offset] = csum >> 8; + data[14+hlen+csum_offset+1] = csum & 0xff; +} diff --git a/net/checksum.h b/net/checksum.h new file mode 100644 index 0000000000..1f052986e6 --- /dev/null +++ b/net/checksum.h @@ -0,0 +1,29 @@ +/* + * IP checksumming functions. + * (c) 2008 Gerd Hoffmann <kraxel@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; under version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef QEMU_NET_CHECKSUM_H +#define QEMU_NET_CHECKSUM_H + +#include <stdint.h> + +uint32_t net_checksum_add(int len, uint8_t *buf); +uint16_t net_checksum_finish(uint32_t sum); +uint16_t net_checksum_tcpudp(uint16_t length, uint16_t proto, + uint8_t *addrs, uint8_t *buf); +void net_checksum_calculate(uint8_t *data, int length); + +#endif /* QEMU_NET_CHECKSUM_H */ diff --git a/net/dump.c b/net/dump.c new file mode 100644 index 0000000000..004231d481 --- /dev/null +++ b/net/dump.c @@ -0,0 +1,185 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ + +#include "dump.h" +#include "qemu-common.h" +#include "qemu-error.h" +#include "qemu-log.h" +#include "qemu-timer.h" +#include "hub.h" + +typedef struct DumpState { + NetClientState nc; + int64_t start_ts; + int fd; + int pcap_caplen; +} DumpState; + +#define PCAP_MAGIC 0xa1b2c3d4 + +struct pcap_file_hdr { + uint32_t magic; + uint16_t version_major; + uint16_t version_minor; + int32_t thiszone; + uint32_t sigfigs; + uint32_t snaplen; + uint32_t linktype; +}; + +struct pcap_sf_pkthdr { + struct { + int32_t tv_sec; + int32_t tv_usec; + } ts; + uint32_t caplen; + uint32_t len; +}; + +static ssize_t dump_receive(NetClientState *nc, const uint8_t *buf, size_t size) +{ + DumpState *s = DO_UPCAST(DumpState, nc, nc); + struct pcap_sf_pkthdr hdr; + int64_t ts; + int caplen; + + /* Early return in case of previous error. */ + if (s->fd < 0) { + return size; + } + + ts = muldiv64(qemu_get_clock_ns(vm_clock), 1000000, get_ticks_per_sec()); + caplen = size > s->pcap_caplen ? s->pcap_caplen : size; + + hdr.ts.tv_sec = ts / 1000000 + s->start_ts; + hdr.ts.tv_usec = ts % 1000000; + hdr.caplen = caplen; + hdr.len = size; + if (write(s->fd, &hdr, sizeof(hdr)) != sizeof(hdr) || + write(s->fd, buf, caplen) != caplen) { + qemu_log("-net dump write error - stop dump\n"); + close(s->fd); + s->fd = -1; + } + + return size; +} + +static void dump_cleanup(NetClientState *nc) +{ + DumpState *s = DO_UPCAST(DumpState, nc, nc); + + close(s->fd); +} + +static NetClientInfo net_dump_info = { + .type = NET_CLIENT_OPTIONS_KIND_DUMP, + .size = sizeof(DumpState), + .receive = dump_receive, + .cleanup = dump_cleanup, +}; + +static int net_dump_init(NetClientState *peer, const char *device, + const char *name, const char *filename, int len) +{ + struct pcap_file_hdr hdr; + NetClientState *nc; + DumpState *s; + struct tm tm; + int fd; + + fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0644); + if (fd < 0) { + error_report("-net dump: can't open %s", filename); + return -1; + } + + hdr.magic = PCAP_MAGIC; + hdr.version_major = 2; + hdr.version_minor = 4; + hdr.thiszone = 0; + hdr.sigfigs = 0; + hdr.snaplen = len; + hdr.linktype = 1; + + if (write(fd, &hdr, sizeof(hdr)) < sizeof(hdr)) { + error_report("-net dump write error: %s", strerror(errno)); + close(fd); + return -1; + } + + nc = qemu_new_net_client(&net_dump_info, peer, device, name); + + snprintf(nc->info_str, sizeof(nc->info_str), + "dump to %s (len=%d)", filename, len); + + s = DO_UPCAST(DumpState, nc, nc); + + s->fd = fd; + s->pcap_caplen = len; + + qemu_get_timedate(&tm, 0); + s->start_ts = mktime(&tm); + + return 0; +} + +int net_init_dump(const NetClientOptions *opts, const char *name, + NetClientState *peer) +{ + int len; + const char *file; + char def_file[128]; + const NetdevDumpOptions *dump; + + assert(opts->kind == NET_CLIENT_OPTIONS_KIND_DUMP); + dump = opts->dump; + + assert(peer); + + if (dump->has_file) { + file = dump->file; + } else { + int id; + int ret; + + ret = net_hub_id_for_client(peer, &id); + assert(ret == 0); /* peer must be on a hub */ + + snprintf(def_file, sizeof(def_file), "qemu-vlan%d.pcap", id); + file = def_file; + } + + if (dump->has_len) { + if (dump->len > INT_MAX) { + error_report("invalid length: %"PRIu64, dump->len); + return -1; + } + len = dump->len; + } else { + len = 65536; + } + + return net_dump_init(peer, "dump", name, file, len); +} diff --git a/net/dump.h b/net/dump.h new file mode 100644 index 0000000000..33f152b460 --- /dev/null +++ b/net/dump.h @@ -0,0 +1,33 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ +#ifndef QEMU_NET_DUMP_H +#define QEMU_NET_DUMP_H + +#include "net.h" +#include "qapi-types.h" + +int net_init_dump(const NetClientOptions *opts, const char *name, + NetClientState *peer); + +#endif /* QEMU_NET_DUMP_H */ diff --git a/net/hub.c b/net/hub.c new file mode 100644 index 0000000000..ac157e32ee --- /dev/null +++ b/net/hub.c @@ -0,0 +1,339 @@ +/* + * Hub net client + * + * Copyright IBM, Corp. 2012 + * + * Authors: + * Stefan Hajnoczi <stefanha@linux.vnet.ibm.com> + * Zhi Yong Wu <wuzhy@linux.vnet.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "monitor.h" +#include "net.h" +#include "hub.h" +#include "iov.h" + +/* + * A hub broadcasts incoming packets to all its ports except the source port. + * Hubs can be used to provide independent network segments, also confusingly + * named the QEMU 'vlan' feature. + */ + +typedef struct NetHub NetHub; + +typedef struct NetHubPort { + NetClientState nc; + QLIST_ENTRY(NetHubPort) next; + NetHub *hub; + int id; +} NetHubPort; + +struct NetHub { + int id; + QLIST_ENTRY(NetHub) next; + int num_ports; + QLIST_HEAD(, NetHubPort) ports; +}; + +static QLIST_HEAD(, NetHub) hubs = QLIST_HEAD_INITIALIZER(&hubs); + +static ssize_t net_hub_receive(NetHub *hub, NetHubPort *source_port, + const uint8_t *buf, size_t len) +{ + NetHubPort *port; + + QLIST_FOREACH(port, &hub->ports, next) { + if (port == source_port) { + continue; + } + + qemu_send_packet(&port->nc, buf, len); + } + return len; +} + +static ssize_t net_hub_receive_iov(NetHub *hub, NetHubPort *source_port, + const struct iovec *iov, int iovcnt) +{ + NetHubPort *port; + ssize_t len = iov_size(iov, iovcnt); + + QLIST_FOREACH(port, &hub->ports, next) { + if (port == source_port) { + continue; + } + + qemu_sendv_packet(&port->nc, iov, iovcnt); + } + return len; +} + +static NetHub *net_hub_new(int id) +{ + NetHub *hub; + + hub = g_malloc(sizeof(*hub)); + hub->id = id; + hub->num_ports = 0; + QLIST_INIT(&hub->ports); + + QLIST_INSERT_HEAD(&hubs, hub, next); + + return hub; +} + +static int net_hub_port_can_receive(NetClientState *nc) +{ + NetHubPort *port; + NetHubPort *src_port = DO_UPCAST(NetHubPort, nc, nc); + NetHub *hub = src_port->hub; + + QLIST_FOREACH(port, &hub->ports, next) { + if (port == src_port) { + continue; + } + + if (!qemu_can_send_packet(&port->nc)) { + return 0; + } + } + + return 1; +} + +static ssize_t net_hub_port_receive(NetClientState *nc, + const uint8_t *buf, size_t len) +{ + NetHubPort *port = DO_UPCAST(NetHubPort, nc, nc); + + return net_hub_receive(port->hub, port, buf, len); +} + +static ssize_t net_hub_port_receive_iov(NetClientState *nc, + const struct iovec *iov, int iovcnt) +{ + NetHubPort *port = DO_UPCAST(NetHubPort, nc, nc); + + return net_hub_receive_iov(port->hub, port, iov, iovcnt); +} + +static void net_hub_port_cleanup(NetClientState *nc) +{ + NetHubPort *port = DO_UPCAST(NetHubPort, nc, nc); + + QLIST_REMOVE(port, next); +} + +static NetClientInfo net_hub_port_info = { + .type = NET_CLIENT_OPTIONS_KIND_HUBPORT, + .size = sizeof(NetHubPort), + .can_receive = net_hub_port_can_receive, + .receive = net_hub_port_receive, + .receive_iov = net_hub_port_receive_iov, + .cleanup = net_hub_port_cleanup, +}; + +static NetHubPort *net_hub_port_new(NetHub *hub, const char *name) +{ + NetClientState *nc; + NetHubPort *port; + int id = hub->num_ports++; + char default_name[128]; + + if (!name) { + snprintf(default_name, sizeof(default_name), + "hub%dport%d", hub->id, id); + name = default_name; + } + + nc = qemu_new_net_client(&net_hub_port_info, NULL, "hub", name); + port = DO_UPCAST(NetHubPort, nc, nc); + port->id = id; + port->hub = hub; + + QLIST_INSERT_HEAD(&hub->ports, port, next); + + return port; +} + +/** + * Create a port on a given hub + * @name: Net client name or NULL for default name. + * + * If there is no existing hub with the given id then a new hub is created. + */ +NetClientState *net_hub_add_port(int hub_id, const char *name) +{ + NetHub *hub; + NetHubPort *port; + + QLIST_FOREACH(hub, &hubs, next) { + if (hub->id == hub_id) { + break; + } + } + + if (!hub) { + hub = net_hub_new(hub_id); + } + + port = net_hub_port_new(hub, name); + return &port->nc; +} + +/** + * Find a specific client on a hub + */ +NetClientState *net_hub_find_client_by_name(int hub_id, const char *name) +{ + NetHub *hub; + NetHubPort *port; + NetClientState *peer; + + QLIST_FOREACH(hub, &hubs, next) { + if (hub->id == hub_id) { + QLIST_FOREACH(port, &hub->ports, next) { + peer = port->nc.peer; + + if (peer && strcmp(peer->name, name) == 0) { + return peer; + } + } + } + } + return NULL; +} + +/** + * Find a available port on a hub; otherwise create one new port + */ +NetClientState *net_hub_port_find(int hub_id) +{ + NetHub *hub; + NetHubPort *port; + NetClientState *nc; + + QLIST_FOREACH(hub, &hubs, next) { + if (hub->id == hub_id) { + QLIST_FOREACH(port, &hub->ports, next) { + nc = port->nc.peer; + if (!nc) { + return &(port->nc); + } + } + break; + } + } + + nc = net_hub_add_port(hub_id, NULL); + return nc; +} + +/** + * Print hub configuration + */ +void net_hub_info(Monitor *mon) +{ + NetHub *hub; + NetHubPort *port; + + QLIST_FOREACH(hub, &hubs, next) { + monitor_printf(mon, "hub %d\n", hub->id); + QLIST_FOREACH(port, &hub->ports, next) { + if (port->nc.peer) { + monitor_printf(mon, " \\ "); + print_net_client(mon, port->nc.peer); + } + } + } +} + +/** + * Get the hub id that a client is connected to + * + * @id Pointer for hub id output, may be NULL + */ +int net_hub_id_for_client(NetClientState *nc, int *id) +{ + NetHubPort *port; + + if (nc->info->type == NET_CLIENT_OPTIONS_KIND_HUBPORT) { + port = DO_UPCAST(NetHubPort, nc, nc); + } else if (nc->peer != NULL && nc->peer->info->type == + NET_CLIENT_OPTIONS_KIND_HUBPORT) { + port = DO_UPCAST(NetHubPort, nc, nc->peer); + } else { + return -ENOENT; + } + + if (id) { + *id = port->hub->id; + } + return 0; +} + +int net_init_hubport(const NetClientOptions *opts, const char *name, + NetClientState *peer) +{ + const NetdevHubPortOptions *hubport; + + assert(opts->kind == NET_CLIENT_OPTIONS_KIND_HUBPORT); + hubport = opts->hubport; + + /* Treat hub port like a backend, NIC must be the one to peer */ + if (peer) { + return -EINVAL; + } + + net_hub_add_port(hubport->hubid, name); + return 0; +} + +/** + * Warn if hub configurations are likely wrong + */ +void net_hub_check_clients(void) +{ + NetHub *hub; + NetHubPort *port; + NetClientState *peer; + + QLIST_FOREACH(hub, &hubs, next) { + int has_nic = 0, has_host_dev = 0; + + QLIST_FOREACH(port, &hub->ports, next) { + peer = port->nc.peer; + if (!peer) { + fprintf(stderr, "Warning: hub port %s has no peer\n", + port->nc.name); + continue; + } + + switch (peer->info->type) { + case NET_CLIENT_OPTIONS_KIND_NIC: + has_nic = 1; + break; + case NET_CLIENT_OPTIONS_KIND_USER: + case NET_CLIENT_OPTIONS_KIND_TAP: + case NET_CLIENT_OPTIONS_KIND_SOCKET: + case NET_CLIENT_OPTIONS_KIND_VDE: + has_host_dev = 1; + break; + default: + break; + } + } + if (has_host_dev && !has_nic) { + fprintf(stderr, "Warning: vlan %d with no nics\n", hub->id); + } + if (has_nic && !has_host_dev) { + fprintf(stderr, + "Warning: vlan %d is not connected to host network\n", + hub->id); + } + } +} diff --git a/net/hub.h b/net/hub.h new file mode 100644 index 0000000000..26a1ade1f9 --- /dev/null +++ b/net/hub.h @@ -0,0 +1,29 @@ +/* + * Hub net client + * + * Copyright IBM, Corp. 2012 + * + * Authors: + * Stefan Hajnoczi <stefanha@linux.vnet.ibm.com> + * Zhi Yong Wu <wuzhy@linux.vnet.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef NET_HUB_H +#define NET_HUB_H + +#include "qemu-common.h" + +int net_init_hubport(const NetClientOptions *opts, const char *name, + NetClientState *peer); +NetClientState *net_hub_add_port(int hub_id, const char *name); +NetClientState *net_hub_find_client_by_name(int hub_id, const char *name); +void net_hub_info(Monitor *mon); +int net_hub_id_for_client(NetClientState *nc, int *id); +void net_hub_check_clients(void); +NetClientState *net_hub_port_find(int hub_id); + +#endif /* NET_HUB_H */ diff --git a/net/queue.c b/net/queue.c new file mode 100644 index 0000000000..e8030aafe4 --- /dev/null +++ b/net/queue.c @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2003-2008 Fabrice Bellard + * Copyright (c) 2009 Red Hat, Inc. + * + * 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. + */ + +#include "net/queue.h" +#include "qemu-queue.h" +#include "net.h" + +/* The delivery handler may only return zero if it will call + * qemu_net_queue_flush() when it determines that it is once again able + * to deliver packets. It must also call qemu_net_queue_purge() in its + * cleanup path. + * + * If a sent callback is provided to send(), the caller must handle a + * zero return from the delivery handler by not sending any more packets + * until we have invoked the callback. Only in that case will we queue + * the packet. + * + * If a sent callback isn't provided, we just drop the packet to avoid + * unbounded queueing. + */ + +struct NetPacket { + QTAILQ_ENTRY(NetPacket) entry; + NetClientState *sender; + unsigned flags; + int size; + NetPacketSent *sent_cb; + uint8_t data[0]; +}; + +struct NetQueue { + void *opaque; + + QTAILQ_HEAD(packets, NetPacket) packets; + + unsigned delivering : 1; +}; + +NetQueue *qemu_new_net_queue(void *opaque) +{ + NetQueue *queue; + + queue = g_malloc0(sizeof(NetQueue)); + + queue->opaque = opaque; + + QTAILQ_INIT(&queue->packets); + + queue->delivering = 0; + + return queue; +} + +void qemu_del_net_queue(NetQueue *queue) +{ + NetPacket *packet, *next; + + QTAILQ_FOREACH_SAFE(packet, &queue->packets, entry, next) { + QTAILQ_REMOVE(&queue->packets, packet, entry); + g_free(packet); + } + + g_free(queue); +} + +static ssize_t qemu_net_queue_append(NetQueue *queue, + NetClientState *sender, + unsigned flags, + const uint8_t *buf, + size_t size, + NetPacketSent *sent_cb) +{ + NetPacket *packet; + + packet = g_malloc(sizeof(NetPacket) + size); + packet->sender = sender; + packet->flags = flags; + packet->size = size; + packet->sent_cb = sent_cb; + memcpy(packet->data, buf, size); + + QTAILQ_INSERT_TAIL(&queue->packets, packet, entry); + + return size; +} + +static ssize_t qemu_net_queue_append_iov(NetQueue *queue, + NetClientState *sender, + unsigned flags, + const struct iovec *iov, + int iovcnt, + NetPacketSent *sent_cb) +{ + NetPacket *packet; + size_t max_len = 0; + int i; + + for (i = 0; i < iovcnt; i++) { + max_len += iov[i].iov_len; + } + + packet = g_malloc(sizeof(NetPacket) + max_len); + packet->sender = sender; + packet->sent_cb = sent_cb; + packet->flags = flags; + packet->size = 0; + + for (i = 0; i < iovcnt; i++) { + size_t len = iov[i].iov_len; + + memcpy(packet->data + packet->size, iov[i].iov_base, len); + packet->size += len; + } + + QTAILQ_INSERT_TAIL(&queue->packets, packet, entry); + + return packet->size; +} + +static ssize_t qemu_net_queue_deliver(NetQueue *queue, + NetClientState *sender, + unsigned flags, + const uint8_t *data, + size_t size) +{ + ssize_t ret = -1; + + queue->delivering = 1; + ret = qemu_deliver_packet(sender, flags, data, size, queue->opaque); + queue->delivering = 0; + + return ret; +} + +static ssize_t qemu_net_queue_deliver_iov(NetQueue *queue, + NetClientState *sender, + unsigned flags, + const struct iovec *iov, + int iovcnt) +{ + ssize_t ret = -1; + + queue->delivering = 1; + ret = qemu_deliver_packet_iov(sender, flags, iov, iovcnt, queue->opaque); + queue->delivering = 0; + + return ret; +} + +ssize_t qemu_net_queue_send(NetQueue *queue, + NetClientState *sender, + unsigned flags, + const uint8_t *data, + size_t size, + NetPacketSent *sent_cb) +{ + ssize_t ret; + + if (queue->delivering || !qemu_can_send_packet(sender)) { + return qemu_net_queue_append(queue, sender, flags, data, size, sent_cb); + } + + ret = qemu_net_queue_deliver(queue, sender, flags, data, size); + if (ret == 0) { + qemu_net_queue_append(queue, sender, flags, data, size, sent_cb); + return 0; + } + + qemu_net_queue_flush(queue); + + return ret; +} + +ssize_t qemu_net_queue_send_iov(NetQueue *queue, + NetClientState *sender, + unsigned flags, + const struct iovec *iov, + int iovcnt, + NetPacketSent *sent_cb) +{ + ssize_t ret; + + if (queue->delivering || !qemu_can_send_packet(sender)) { + return qemu_net_queue_append_iov(queue, sender, flags, + iov, iovcnt, sent_cb); + } + + ret = qemu_net_queue_deliver_iov(queue, sender, flags, iov, iovcnt); + if (ret == 0) { + qemu_net_queue_append_iov(queue, sender, flags, iov, iovcnt, sent_cb); + return 0; + } + + qemu_net_queue_flush(queue); + + return ret; +} + +void qemu_net_queue_purge(NetQueue *queue, NetClientState *from) +{ + NetPacket *packet, *next; + + QTAILQ_FOREACH_SAFE(packet, &queue->packets, entry, next) { + if (packet->sender == from) { + QTAILQ_REMOVE(&queue->packets, packet, entry); + g_free(packet); + } + } +} + +void qemu_net_queue_flush(NetQueue *queue) +{ + while (!QTAILQ_EMPTY(&queue->packets)) { + NetPacket *packet; + int ret; + + packet = QTAILQ_FIRST(&queue->packets); + QTAILQ_REMOVE(&queue->packets, packet, entry); + + ret = qemu_net_queue_deliver(queue, + packet->sender, + packet->flags, + packet->data, + packet->size); + if (ret == 0) { + QTAILQ_INSERT_HEAD(&queue->packets, packet, entry); + break; + } + + if (packet->sent_cb) { + packet->sent_cb(packet->sender, ret); + } + + g_free(packet); + } +} diff --git a/net/queue.h b/net/queue.h new file mode 100644 index 0000000000..9d44a9b3b8 --- /dev/null +++ b/net/queue.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2003-2008 Fabrice Bellard + * Copyright (c) 2009 Red Hat, Inc. + * + * 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. + */ + +#ifndef QEMU_NET_QUEUE_H +#define QEMU_NET_QUEUE_H + +#include "qemu-common.h" + +typedef struct NetPacket NetPacket; +typedef struct NetQueue NetQueue; + +typedef void (NetPacketSent) (NetClientState *sender, ssize_t ret); + +#define QEMU_NET_PACKET_FLAG_NONE 0 +#define QEMU_NET_PACKET_FLAG_RAW (1<<0) + +NetQueue *qemu_new_net_queue(void *opaque); + +void qemu_del_net_queue(NetQueue *queue); + +ssize_t qemu_net_queue_send(NetQueue *queue, + NetClientState *sender, + unsigned flags, + const uint8_t *data, + size_t size, + NetPacketSent *sent_cb); + +ssize_t qemu_net_queue_send_iov(NetQueue *queue, + NetClientState *sender, + unsigned flags, + const struct iovec *iov, + int iovcnt, + NetPacketSent *sent_cb); + +void qemu_net_queue_purge(NetQueue *queue, NetClientState *from); +void qemu_net_queue_flush(NetQueue *queue); + +#endif /* QEMU_NET_QUEUE_H */ diff --git a/net/slirp.c b/net/slirp.c new file mode 100644 index 0000000000..8db66ea539 --- /dev/null +++ b/net/slirp.c @@ -0,0 +1,762 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ +#include "net/slirp.h" + +#include "config-host.h" + +#ifndef _WIN32 +#include <pwd.h> +#include <sys/wait.h> +#endif +#include "net.h" +#include "net/hub.h" +#include "monitor.h" +#include "qemu_socket.h" +#include "slirp/libslirp.h" + +static int get_str_sep(char *buf, int buf_size, const char **pp, int sep) +{ + const char *p, *p1; + int len; + p = *pp; + p1 = strchr(p, sep); + if (!p1) + return -1; + len = p1 - p; + p1++; + if (buf_size > 0) { + if (len > buf_size - 1) + len = buf_size - 1; + memcpy(buf, p, len); + buf[len] = '\0'; + } + *pp = p1; + return 0; +} + +/* slirp network adapter */ + +#define SLIRP_CFG_HOSTFWD 1 +#define SLIRP_CFG_LEGACY 2 + +struct slirp_config_str { + struct slirp_config_str *next; + int flags; + char str[1024]; + int legacy_format; +}; + +typedef struct SlirpState { + NetClientState nc; + QTAILQ_ENTRY(SlirpState) entry; + Slirp *slirp; +#ifndef _WIN32 + char smb_dir[128]; +#endif +} SlirpState; + +static struct slirp_config_str *slirp_configs; +const char *legacy_tftp_prefix; +const char *legacy_bootp_filename; +static QTAILQ_HEAD(slirp_stacks, SlirpState) slirp_stacks = + QTAILQ_HEAD_INITIALIZER(slirp_stacks); + +static int slirp_hostfwd(SlirpState *s, const char *redir_str, + int legacy_format); +static int slirp_guestfwd(SlirpState *s, const char *config_str, + int legacy_format); + +#ifndef _WIN32 +static const char *legacy_smb_export; + +static int slirp_smb(SlirpState *s, const char *exported_dir, + struct in_addr vserver_addr); +static void slirp_smb_cleanup(SlirpState *s); +#else +static inline void slirp_smb_cleanup(SlirpState *s) { } +#endif + +void slirp_output(void *opaque, const uint8_t *pkt, int pkt_len) +{ + SlirpState *s = opaque; + + qemu_send_packet(&s->nc, pkt, pkt_len); +} + +static ssize_t net_slirp_receive(NetClientState *nc, const uint8_t *buf, size_t size) +{ + SlirpState *s = DO_UPCAST(SlirpState, nc, nc); + + slirp_input(s->slirp, buf, size); + + return size; +} + +static void net_slirp_cleanup(NetClientState *nc) +{ + SlirpState *s = DO_UPCAST(SlirpState, nc, nc); + + slirp_cleanup(s->slirp); + slirp_smb_cleanup(s); + QTAILQ_REMOVE(&slirp_stacks, s, entry); +} + +static NetClientInfo net_slirp_info = { + .type = NET_CLIENT_OPTIONS_KIND_USER, + .size = sizeof(SlirpState), + .receive = net_slirp_receive, + .cleanup = net_slirp_cleanup, +}; + +static int net_slirp_init(NetClientState *peer, const char *model, + const char *name, int restricted, + const char *vnetwork, const char *vhost, + const char *vhostname, const char *tftp_export, + const char *bootfile, const char *vdhcp_start, + const char *vnameserver, const char *smb_export, + const char *vsmbserver) +{ + /* default settings according to historic slirp */ + struct in_addr net = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */ + struct in_addr mask = { .s_addr = htonl(0xffffff00) }; /* 255.255.255.0 */ + struct in_addr host = { .s_addr = htonl(0x0a000202) }; /* 10.0.2.2 */ + struct in_addr dhcp = { .s_addr = htonl(0x0a00020f) }; /* 10.0.2.15 */ + struct in_addr dns = { .s_addr = htonl(0x0a000203) }; /* 10.0.2.3 */ +#ifndef _WIN32 + struct in_addr smbsrv = { .s_addr = 0 }; +#endif + NetClientState *nc; + SlirpState *s; + char buf[20]; + uint32_t addr; + int shift; + char *end; + struct slirp_config_str *config; + + if (!tftp_export) { + tftp_export = legacy_tftp_prefix; + } + if (!bootfile) { + bootfile = legacy_bootp_filename; + } + + if (vnetwork) { + if (get_str_sep(buf, sizeof(buf), &vnetwork, '/') < 0) { + if (!inet_aton(vnetwork, &net)) { + return -1; + } + addr = ntohl(net.s_addr); + if (!(addr & 0x80000000)) { + mask.s_addr = htonl(0xff000000); /* class A */ + } else if ((addr & 0xfff00000) == 0xac100000) { + mask.s_addr = htonl(0xfff00000); /* priv. 172.16.0.0/12 */ + } else if ((addr & 0xc0000000) == 0x80000000) { + mask.s_addr = htonl(0xffff0000); /* class B */ + } else if ((addr & 0xffff0000) == 0xc0a80000) { + mask.s_addr = htonl(0xffff0000); /* priv. 192.168.0.0/16 */ + } else if ((addr & 0xffff0000) == 0xc6120000) { + mask.s_addr = htonl(0xfffe0000); /* tests 198.18.0.0/15 */ + } else if ((addr & 0xe0000000) == 0xe0000000) { + mask.s_addr = htonl(0xffffff00); /* class C */ + } else { + mask.s_addr = htonl(0xfffffff0); /* multicast/reserved */ + } + } else { + if (!inet_aton(buf, &net)) { + return -1; + } + shift = strtol(vnetwork, &end, 10); + if (*end != '\0') { + if (!inet_aton(vnetwork, &mask)) { + return -1; + } + } else if (shift < 4 || shift > 32) { + return -1; + } else { + mask.s_addr = htonl(0xffffffff << (32 - shift)); + } + } + net.s_addr &= mask.s_addr; + host.s_addr = net.s_addr | (htonl(0x0202) & ~mask.s_addr); + dhcp.s_addr = net.s_addr | (htonl(0x020f) & ~mask.s_addr); + dns.s_addr = net.s_addr | (htonl(0x0203) & ~mask.s_addr); + } + + if (vhost && !inet_aton(vhost, &host)) { + return -1; + } + if ((host.s_addr & mask.s_addr) != net.s_addr) { + return -1; + } + + if (vdhcp_start && !inet_aton(vdhcp_start, &dhcp)) { + return -1; + } + if ((dhcp.s_addr & mask.s_addr) != net.s_addr || + dhcp.s_addr == host.s_addr || dhcp.s_addr == dns.s_addr) { + return -1; + } + + if (vnameserver && !inet_aton(vnameserver, &dns)) { + return -1; + } + if ((dns.s_addr & mask.s_addr) != net.s_addr || + dns.s_addr == host.s_addr) { + return -1; + } + +#ifndef _WIN32 + if (vsmbserver && !inet_aton(vsmbserver, &smbsrv)) { + return -1; + } +#endif + + nc = qemu_new_net_client(&net_slirp_info, peer, model, name); + + snprintf(nc->info_str, sizeof(nc->info_str), + "net=%s,restrict=%s", inet_ntoa(net), + restricted ? "on" : "off"); + + s = DO_UPCAST(SlirpState, nc, nc); + + s->slirp = slirp_init(restricted, net, mask, host, vhostname, + tftp_export, bootfile, dhcp, dns, s); + QTAILQ_INSERT_TAIL(&slirp_stacks, s, entry); + + for (config = slirp_configs; config; config = config->next) { + if (config->flags & SLIRP_CFG_HOSTFWD) { + if (slirp_hostfwd(s, config->str, + config->flags & SLIRP_CFG_LEGACY) < 0) + goto error; + } else { + if (slirp_guestfwd(s, config->str, + config->flags & SLIRP_CFG_LEGACY) < 0) + goto error; + } + } +#ifndef _WIN32 + if (!smb_export) { + smb_export = legacy_smb_export; + } + if (smb_export) { + if (slirp_smb(s, smb_export, smbsrv) < 0) + goto error; + } +#endif + + return 0; + +error: + qemu_del_net_client(nc); + return -1; +} + +static SlirpState *slirp_lookup(Monitor *mon, const char *vlan, + const char *stack) +{ + + if (vlan) { + NetClientState *nc; + nc = net_hub_find_client_by_name(strtol(vlan, NULL, 0), stack); + if (!nc) { + return NULL; + } + if (strcmp(nc->model, "user")) { + monitor_printf(mon, "invalid device specified\n"); + return NULL; + } + return DO_UPCAST(SlirpState, nc, nc); + } else { + if (QTAILQ_EMPTY(&slirp_stacks)) { + monitor_printf(mon, "user mode network stack not in use\n"); + return NULL; + } + return QTAILQ_FIRST(&slirp_stacks); + } +} + +void net_slirp_hostfwd_remove(Monitor *mon, const QDict *qdict) +{ + struct in_addr host_addr = { .s_addr = INADDR_ANY }; + int host_port; + char buf[256]; + const char *src_str, *p; + SlirpState *s; + int is_udp = 0; + int err; + const char *arg1 = qdict_get_str(qdict, "arg1"); + const char *arg2 = qdict_get_try_str(qdict, "arg2"); + const char *arg3 = qdict_get_try_str(qdict, "arg3"); + + if (arg2) { + s = slirp_lookup(mon, arg1, arg2); + src_str = arg3; + } else { + s = slirp_lookup(mon, NULL, NULL); + src_str = arg1; + } + if (!s) { + return; + } + + p = src_str; + if (!p || get_str_sep(buf, sizeof(buf), &p, ':') < 0) { + goto fail_syntax; + } + + if (!strcmp(buf, "tcp") || buf[0] == '\0') { + is_udp = 0; + } else if (!strcmp(buf, "udp")) { + is_udp = 1; + } else { + goto fail_syntax; + } + + if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) { + goto fail_syntax; + } + if (buf[0] != '\0' && !inet_aton(buf, &host_addr)) { + goto fail_syntax; + } + + host_port = atoi(p); + + err = slirp_remove_hostfwd(QTAILQ_FIRST(&slirp_stacks)->slirp, is_udp, + host_addr, host_port); + + monitor_printf(mon, "host forwarding rule for %s %s\n", src_str, + err ? "not found" : "removed"); + return; + + fail_syntax: + monitor_printf(mon, "invalid format\n"); +} + +static int slirp_hostfwd(SlirpState *s, const char *redir_str, + int legacy_format) +{ + struct in_addr host_addr = { .s_addr = INADDR_ANY }; + struct in_addr guest_addr = { .s_addr = 0 }; + int host_port, guest_port; + const char *p; + char buf[256]; + int is_udp; + char *end; + + p = redir_str; + if (!p || get_str_sep(buf, sizeof(buf), &p, ':') < 0) { + goto fail_syntax; + } + if (!strcmp(buf, "tcp") || buf[0] == '\0') { + is_udp = 0; + } else if (!strcmp(buf, "udp")) { + is_udp = 1; + } else { + goto fail_syntax; + } + + if (!legacy_format) { + if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) { + goto fail_syntax; + } + if (buf[0] != '\0' && !inet_aton(buf, &host_addr)) { + goto fail_syntax; + } + } + + if (get_str_sep(buf, sizeof(buf), &p, legacy_format ? ':' : '-') < 0) { + goto fail_syntax; + } + host_port = strtol(buf, &end, 0); + if (*end != '\0' || host_port < 1 || host_port > 65535) { + goto fail_syntax; + } + + if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) { + goto fail_syntax; + } + if (buf[0] != '\0' && !inet_aton(buf, &guest_addr)) { + goto fail_syntax; + } + + guest_port = strtol(p, &end, 0); + if (*end != '\0' || guest_port < 1 || guest_port > 65535) { + goto fail_syntax; + } + + if (slirp_add_hostfwd(s->slirp, is_udp, host_addr, host_port, guest_addr, + guest_port) < 0) { + error_report("could not set up host forwarding rule '%s'", + redir_str); + return -1; + } + return 0; + + fail_syntax: + error_report("invalid host forwarding rule '%s'", redir_str); + return -1; +} + +void net_slirp_hostfwd_add(Monitor *mon, const QDict *qdict) +{ + const char *redir_str; + SlirpState *s; + const char *arg1 = qdict_get_str(qdict, "arg1"); + const char *arg2 = qdict_get_try_str(qdict, "arg2"); + const char *arg3 = qdict_get_try_str(qdict, "arg3"); + + if (arg2) { + s = slirp_lookup(mon, arg1, arg2); + redir_str = arg3; + } else { + s = slirp_lookup(mon, NULL, NULL); + redir_str = arg1; + } + if (s) { + slirp_hostfwd(s, redir_str, 0); + } + +} + +int net_slirp_redir(const char *redir_str) +{ + struct slirp_config_str *config; + + if (QTAILQ_EMPTY(&slirp_stacks)) { + config = g_malloc(sizeof(*config)); + pstrcpy(config->str, sizeof(config->str), redir_str); + config->flags = SLIRP_CFG_HOSTFWD | SLIRP_CFG_LEGACY; + config->next = slirp_configs; + slirp_configs = config; + return 0; + } + + return slirp_hostfwd(QTAILQ_FIRST(&slirp_stacks), redir_str, 1); +} + +#ifndef _WIN32 + +/* automatic user mode samba server configuration */ +static void slirp_smb_cleanup(SlirpState *s) +{ + char cmd[128]; + int ret; + + if (s->smb_dir[0] != '\0') { + snprintf(cmd, sizeof(cmd), "rm -rf %s", s->smb_dir); + ret = system(cmd); + if (ret == -1 || !WIFEXITED(ret)) { + error_report("'%s' failed.", cmd); + } else if (WEXITSTATUS(ret)) { + error_report("'%s' failed. Error code: %d", + cmd, WEXITSTATUS(ret)); + } + s->smb_dir[0] = '\0'; + } +} + +static int slirp_smb(SlirpState* s, const char *exported_dir, + struct in_addr vserver_addr) +{ + static int instance; + char smb_conf[128]; + char smb_cmdline[128]; + struct passwd *passwd; + FILE *f; + + passwd = getpwuid(geteuid()); + if (!passwd) { + error_report("failed to retrieve user name"); + return -1; + } + + if (access(CONFIG_SMBD_COMMAND, F_OK)) { + error_report("could not find '%s', please install it", + CONFIG_SMBD_COMMAND); + return -1; + } + + if (access(exported_dir, R_OK | X_OK)) { + error_report("error accessing shared directory '%s': %s", + exported_dir, strerror(errno)); + return -1; + } + + snprintf(s->smb_dir, sizeof(s->smb_dir), "/tmp/qemu-smb.%ld-%d", + (long)getpid(), instance++); + if (mkdir(s->smb_dir, 0700) < 0) { + error_report("could not create samba server dir '%s'", s->smb_dir); + return -1; + } + snprintf(smb_conf, sizeof(smb_conf), "%s/%s", s->smb_dir, "smb.conf"); + + f = fopen(smb_conf, "w"); + if (!f) { + slirp_smb_cleanup(s); + error_report("could not create samba server configuration file '%s'", + smb_conf); + return -1; + } + fprintf(f, + "[global]\n" + "private dir=%s\n" + "socket address=127.0.0.1\n" + "pid directory=%s\n" + "lock directory=%s\n" + "state directory=%s\n" + "log file=%s/log.smbd\n" + "smb passwd file=%s/smbpasswd\n" + "security = share\n" + "[qemu]\n" + "path=%s\n" + "read only=no\n" + "guest ok=yes\n" + "force user=%s\n", + s->smb_dir, + s->smb_dir, + s->smb_dir, + s->smb_dir, + s->smb_dir, + s->smb_dir, + exported_dir, + passwd->pw_name + ); + fclose(f); + + snprintf(smb_cmdline, sizeof(smb_cmdline), "%s -s %s", + CONFIG_SMBD_COMMAND, smb_conf); + + if (slirp_add_exec(s->slirp, 0, smb_cmdline, &vserver_addr, 139) < 0) { + slirp_smb_cleanup(s); + error_report("conflicting/invalid smbserver address"); + return -1; + } + return 0; +} + +/* automatic user mode samba server configuration (legacy interface) */ +int net_slirp_smb(const char *exported_dir) +{ + struct in_addr vserver_addr = { .s_addr = 0 }; + + if (legacy_smb_export) { + fprintf(stderr, "-smb given twice\n"); + return -1; + } + legacy_smb_export = exported_dir; + if (!QTAILQ_EMPTY(&slirp_stacks)) { + return slirp_smb(QTAILQ_FIRST(&slirp_stacks), exported_dir, + vserver_addr); + } + return 0; +} + +#endif /* !defined(_WIN32) */ + +struct GuestFwd { + CharDriverState *hd; + struct in_addr server; + int port; + Slirp *slirp; +}; + +static int guestfwd_can_read(void *opaque) +{ + struct GuestFwd *fwd = opaque; + return slirp_socket_can_recv(fwd->slirp, fwd->server, fwd->port); +} + +static void guestfwd_read(void *opaque, const uint8_t *buf, int size) +{ + struct GuestFwd *fwd = opaque; + slirp_socket_recv(fwd->slirp, fwd->server, fwd->port, buf, size); +} + +static int slirp_guestfwd(SlirpState *s, const char *config_str, + int legacy_format) +{ + struct in_addr server = { .s_addr = 0 }; + struct GuestFwd *fwd; + const char *p; + char buf[128]; + char *end; + int port; + + p = config_str; + if (legacy_format) { + if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) { + goto fail_syntax; + } + } else { + if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) { + goto fail_syntax; + } + if (strcmp(buf, "tcp") && buf[0] != '\0') { + goto fail_syntax; + } + if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) { + goto fail_syntax; + } + if (buf[0] != '\0' && !inet_aton(buf, &server)) { + goto fail_syntax; + } + if (get_str_sep(buf, sizeof(buf), &p, '-') < 0) { + goto fail_syntax; + } + } + port = strtol(buf, &end, 10); + if (*end != '\0' || port < 1 || port > 65535) { + goto fail_syntax; + } + + fwd = g_malloc(sizeof(struct GuestFwd)); + snprintf(buf, sizeof(buf), "guestfwd.tcp.%d", port); + + if ((strlen(p) > 4) && !strncmp(p, "cmd:", 4)) { + if (slirp_add_exec(s->slirp, 0, &p[4], &server, port) < 0) { + error_report("conflicting/invalid host:port in guest forwarding " + "rule '%s'", config_str); + g_free(fwd); + return -1; + } + } else { + fwd->hd = qemu_chr_new(buf, p, NULL); + if (!fwd->hd) { + error_report("could not open guest forwarding device '%s'", buf); + g_free(fwd); + return -1; + } + + if (slirp_add_exec(s->slirp, 3, fwd->hd, &server, port) < 0) { + error_report("conflicting/invalid host:port in guest forwarding " + "rule '%s'", config_str); + g_free(fwd); + return -1; + } + fwd->server = server; + fwd->port = port; + fwd->slirp = s->slirp; + + qemu_chr_add_handlers(fwd->hd, guestfwd_can_read, guestfwd_read, + NULL, fwd); + } + return 0; + + fail_syntax: + error_report("invalid guest forwarding rule '%s'", config_str); + return -1; +} + +void do_info_usernet(Monitor *mon) +{ + SlirpState *s; + + QTAILQ_FOREACH(s, &slirp_stacks, entry) { + int id; + bool got_vlan_id = net_hub_id_for_client(&s->nc, &id) == 0; + monitor_printf(mon, "VLAN %d (%s):\n", + got_vlan_id ? id : -1, + s->nc.name); + slirp_connection_info(s->slirp, mon); + } +} + +static void +net_init_slirp_configs(const StringList *fwd, int flags) +{ + while (fwd) { + struct slirp_config_str *config; + + config = g_malloc0(sizeof(*config)); + pstrcpy(config->str, sizeof(config->str), fwd->value->str); + config->flags = flags; + config->next = slirp_configs; + slirp_configs = config; + + fwd = fwd->next; + } +} + +int net_init_slirp(const NetClientOptions *opts, const char *name, + NetClientState *peer) +{ + struct slirp_config_str *config; + char *vnet; + int ret; + const NetdevUserOptions *user; + + assert(opts->kind == NET_CLIENT_OPTIONS_KIND_USER); + user = opts->user; + + vnet = user->has_net ? g_strdup(user->net) : + user->has_ip ? g_strdup_printf("%s/24", user->ip) : + NULL; + + /* all optional fields are initialized to "all bits zero" */ + + net_init_slirp_configs(user->hostfwd, SLIRP_CFG_HOSTFWD); + net_init_slirp_configs(user->guestfwd, 0); + + ret = net_slirp_init(peer, "user", name, user->q_restrict, vnet, + user->host, user->hostname, user->tftp, + user->bootfile, user->dhcpstart, user->dns, user->smb, + user->smbserver); + + while (slirp_configs) { + config = slirp_configs; + slirp_configs = config->next; + g_free(config); + } + + g_free(vnet); + + return ret; +} + +int net_slirp_parse_legacy(QemuOptsList *opts_list, const char *optarg, int *ret) +{ + if (strcmp(opts_list->name, "net") != 0 || + strncmp(optarg, "channel,", strlen("channel,")) != 0) { + return 0; + } + + /* handle legacy -net channel,port:chr */ + optarg += strlen("channel,"); + + if (QTAILQ_EMPTY(&slirp_stacks)) { + struct slirp_config_str *config; + + config = g_malloc(sizeof(*config)); + pstrcpy(config->str, sizeof(config->str), optarg); + config->flags = SLIRP_CFG_LEGACY; + config->next = slirp_configs; + slirp_configs = config; + *ret = 0; + } else { + *ret = slirp_guestfwd(QTAILQ_FIRST(&slirp_stacks), optarg, 1); + } + + return 1; +} + diff --git a/net/slirp.h b/net/slirp.h new file mode 100644 index 0000000000..5f685c4fb1 --- /dev/null +++ b/net/slirp.h @@ -0,0 +1,50 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ +#ifndef QEMU_NET_SLIRP_H +#define QEMU_NET_SLIRP_H + +#include "qemu-common.h" +#include "qdict.h" +#include "qemu-option.h" +#include "qapi-types.h" + +#ifdef CONFIG_SLIRP + +int net_init_slirp(const NetClientOptions *opts, const char *name, + NetClientState *peer); + +void net_slirp_hostfwd_add(Monitor *mon, const QDict *qdict); +void net_slirp_hostfwd_remove(Monitor *mon, const QDict *qdict); + +int net_slirp_redir(const char *redir_str); + +int net_slirp_parse_legacy(QemuOptsList *opts_list, const char *optarg, int *ret); + +int net_slirp_smb(const char *exported_dir); + +void do_info_usernet(Monitor *mon); + +#endif + +#endif /* QEMU_NET_SLIRP_H */ diff --git a/net/socket.c b/net/socket.c new file mode 100644 index 0000000000..c172c249be --- /dev/null +++ b/net/socket.c @@ -0,0 +1,674 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ +#include "net/socket.h" + +#include "config-host.h" + +#include "net.h" +#include "monitor.h" +#include "qemu-char.h" +#include "qemu-common.h" +#include "qemu-error.h" +#include "qemu-option.h" +#include "qemu_socket.h" + +typedef struct NetSocketState { + NetClientState nc; + int listen_fd; + int fd; + int state; /* 0 = getting length, 1 = getting data */ + unsigned int index; + unsigned int packet_len; + uint8_t buf[4096]; + struct sockaddr_in dgram_dst; /* contains inet host and port destination iff connectionless (SOCK_DGRAM) */ +} NetSocketState; + +static void net_socket_accept(void *opaque); + +/* XXX: we consider we can send the whole packet without blocking */ +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; + len = htonl(size); + + send_all(s->fd, (const uint8_t *)&len, sizeof(len)); + return send_all(s->fd, buf, size); +} + +static ssize_t net_socket_receive_dgram(NetClientState *nc, const uint8_t *buf, size_t size) +{ + NetSocketState *s = DO_UPCAST(NetSocketState, nc, nc); + + return sendto(s->fd, (const void *)buf, size, 0, + (struct sockaddr *)&s->dgram_dst, sizeof(s->dgram_dst)); +} + +static void net_socket_send(void *opaque) +{ + NetSocketState *s = opaque; + int size, err; + unsigned l; + uint8_t buf1[4096]; + const uint8_t *buf; + + size = qemu_recv(s->fd, buf1, sizeof(buf1), 0); + if (size < 0) { + err = socket_error(); + if (err != EWOULDBLOCK) + goto eoc; + } else if (size == 0) { + /* 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; + while (size > 0) { + /* reassemble a packet from the network */ + switch(s->state) { + case 0: + l = 4 - s->index; + if (l > size) + l = size; + memcpy(s->buf + s->index, buf, l); + buf += l; + size -= l; + s->index += l; + if (s->index == 4) { + /* got length */ + s->packet_len = ntohl(*(uint32_t *)s->buf); + s->index = 0; + s->state = 1; + } + break; + case 1: + l = s->packet_len - s->index; + if (l > size) + l = size; + if (s->index + l <= sizeof(s->buf)) { + memcpy(s->buf + s->index, buf, l); + } else { + fprintf(stderr, "serious error: oversized packet received," + "connection terminated.\n"); + s->state = 0; + goto eoc; + } + + s->index += l; + buf += l; + size -= l; + if (s->index >= s->packet_len) { + qemu_send_packet(&s->nc, s->buf, s->packet_len); + s->index = 0; + s->state = 0; + } + break; + } + } +} + +static void net_socket_send_dgram(void *opaque) +{ + NetSocketState *s = opaque; + int size; + + size = qemu_recv(s->fd, s->buf, sizeof(s->buf), 0); + if (size < 0) + return; + if (size == 0) { + /* end of connection */ + qemu_set_fd_handler(s->fd, NULL, NULL, NULL); + return; + } + qemu_send_packet(&s->nc, s->buf, size); +} + +static int net_socket_mcast_create(struct sockaddr_in *mcastaddr, struct in_addr *localaddr) +{ + 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), + (int)ntohl(mcastaddr->sin_addr.s_addr)); + 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)"); + goto fail; + } + + ret = bind(fd, (struct sockaddr *)mcastaddr, sizeof(*mcastaddr)); + if (ret < 0) { + perror("bind"); + goto fail; + } + + /* Add host to multicast group */ + imr.imr_multiaddr = mcastaddr->sin_addr; + if (localaddr) { + imr.imr_interface = *localaddr; + } else { + imr.imr_interface.s_addr = htonl(INADDR_ANY); + } + + 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; + } + + /* Force mcast msgs to loopback (eg. several QEMUs in same host */ + loop = 1; + ret=setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, + (const char *)&loop, sizeof(loop)); + if (ret < 0) { + perror("setsockopt(SOL_IP, IP_MULTICAST_LOOP)"); + goto fail; + } + + /* If a bind address is given, only send packets from that address */ + if (localaddr != NULL) { + ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, + (const char *)localaddr, sizeof(*localaddr)); + if (ret < 0) { + perror("setsockopt(IP_MULTICAST_IF)"); + goto fail; + } + } + + socket_set_nonblock(fd); + return fd; +fail: + if (fd >= 0) + closesocket(fd); + return -1; +} + +static void net_socket_cleanup(NetClientState *nc) +{ + NetSocketState *s = DO_UPCAST(NetSocketState, nc, nc); + 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_OPTIONS_KIND_SOCKET, + .size = sizeof(NetSocketState), + .receive = net_socket_receive_dgram, + .cleanup = net_socket_cleanup, +}; + +static NetSocketState *net_socket_fd_init_dgram(NetClientState *peer, + const char *model, + const char *name, + int fd, int is_connected) +{ + struct sockaddr_in saddr; + int newfd; + socklen_t saddr_len; + NetClientState *nc; + NetSocketState *s; + + /* fd passed: multicast: "learn" dgram_dst address from bound address and save it + * Because this may be "shared" socket from a "master" process, datagrams would be recv() + * by ONLY ONE process: we must "clone" this dgram socket --jjo + */ + + 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); + 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)); + + 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; + } + + return s; + +err: + closesocket(fd); + return NULL; +} + +static void net_socket_connect(void *opaque) +{ + NetSocketState *s = opaque; + qemu_set_fd_handler(s->fd, net_socket_send, NULL, s); +} + +static NetClientInfo net_socket_info = { + .type = NET_CLIENT_OPTIONS_KIND_SOCKET, + .size = sizeof(NetSocketState), + .receive = net_socket_receive, + .cleanup = net_socket_cleanup, +}; + +static NetSocketState *net_socket_fd_init_stream(NetClientState *peer, + const char *model, + const char *name, + int fd, int is_connected) +{ + NetClientState *nc; + NetSocketState *s; + + 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); + } else { + qemu_set_fd_handler(s->fd, NULL, net_socket_connect, s); + } + return s; +} + +static NetSocketState *net_socket_fd_init(NetClientState *peer, + const char *model, const char *name, + int fd, int is_connected) +{ + int so_type = -1, optlen=sizeof(so_type); + + 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); + closesocket(fd); + return NULL; + } + switch(so_type) { + case SOCK_DGRAM: + return net_socket_fd_init_dgram(peer, model, name, fd, is_connected); + case SOCK_STREAM: + 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(peer, model, name, fd, is_connected); + } + return NULL; +} + +static void net_socket_accept(void *opaque) +{ + NetSocketState *s = opaque; + struct sockaddr_in saddr; + socklen_t len; + int fd; + + for(;;) { + len = sizeof(saddr); + 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; + } + } + + 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(NetClientState *peer, + const char *model, + const char *name, + const char *host_str) +{ + NetClientState *nc; + NetSocketState *s; + struct sockaddr_in saddr; + int fd, val, ret; + + if (parse_host_port(&saddr, host_str) < 0) + return -1; + + fd = qemu_socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) { + perror("socket"); + return -1; + } + socket_set_nonblock(fd); + + /* allow fast reuse */ + val = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&val, sizeof(val)); + + 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; + } + + 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(NetClientState *peer, + const char *model, + const char *name, + const char *host_str) +{ + NetSocketState *s; + int fd, connected, ret, err; + struct sockaddr_in saddr; + + if (parse_host_port(&saddr, host_str) < 0) + return -1; + + fd = qemu_socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) { + perror("socket"); + return -1; + } + socket_set_nonblock(fd); + + connected = 0; + for(;;) { + ret = connect(fd, (struct sockaddr *)&saddr, sizeof(saddr)); + if (ret < 0) { + err = socket_error(); + if (err == EINTR || err == EWOULDBLOCK) { + } else if (err == EINPROGRESS) { + break; +#ifdef _WIN32 + } else if (err == WSAEALREADY || err == WSAEINVAL) { + break; +#endif + } else { + perror("connect"); + closesocket(fd); + return -1; + } + } else { + connected = 1; + break; + } + } + s = net_socket_fd_init(peer, model, name, fd, connected); + if (!s) + return -1; + snprintf(s->nc.info_str, sizeof(s->nc.info_str), + "socket: connect to %s:%d", + inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port)); + return 0; +} + +static int net_socket_mcast_init(NetClientState *peer, + const char *model, + const char *name, + const char *host_str, + const char *localaddr_str) +{ + NetSocketState *s; + int fd; + struct sockaddr_in saddr; + struct in_addr localaddr, *param_localaddr; + + if (parse_host_port(&saddr, host_str) < 0) + return -1; + + if (localaddr_str != NULL) { + if (inet_aton(localaddr_str, &localaddr) == 0) + return -1; + param_localaddr = &localaddr; + } else { + param_localaddr = NULL; + } + + fd = net_socket_mcast_create(&saddr, param_localaddr); + if (fd < 0) + return -1; + + s = net_socket_fd_init(peer, model, name, fd, 0); + if (!s) + return -1; + + s->dgram_dst = saddr; + + snprintf(s->nc.info_str, sizeof(s->nc.info_str), + "socket: mcast=%s:%d", + inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port)); + return 0; + +} + +static int net_socket_udp_init(NetClientState *peer, + const char *model, + const char *name, + const char *rhost, + const char *lhost) +{ + NetSocketState *s; + int fd, val, ret; + struct sockaddr_in laddr, raddr; + + if (parse_host_port(&laddr, lhost) < 0) { + return -1; + } + + if (parse_host_port(&raddr, rhost) < 0) { + 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; + } + + s = net_socket_fd_init(peer, model, name, fd, 0); + if (!s) { + 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; + + 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 (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; + } + return 0; + } + + if (sock->has_listen) { + if (net_socket_listen_init(peer, "socket", name, sock->listen) == -1) { + return -1; + } + return 0; + } + + if (sock->has_connect) { + if (net_socket_connect_init(peer, "socket", name, sock->connect) == + -1) { + return -1; + } + return 0; + } + + 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; + } + 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; +} diff --git a/net/socket.h b/net/socket.h new file mode 100644 index 0000000000..3f8a092459 --- /dev/null +++ b/net/socket.h @@ -0,0 +1,33 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ +#ifndef QEMU_NET_SOCKET_H +#define QEMU_NET_SOCKET_H + +#include "net.h" +#include "qapi-types.h" + +int net_init_socket(const NetClientOptions *opts, const char *name, + NetClientState *peer); + +#endif /* QEMU_NET_SOCKET_H */ diff --git a/net/tap-aix.c b/net/tap-aix.c new file mode 100644 index 0000000000..f27c17729e --- /dev/null +++ b/net/tap-aix.c @@ -0,0 +1,61 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ + +#include "net/tap.h" +#include <stdio.h> + +int tap_open(char *ifname, int ifname_size, int *vnet_hdr, int vnet_hdr_required) +{ + fprintf(stderr, "no tap on AIX\n"); + return -1; +} + +int tap_set_sndbuf(int fd, const NetdevTapOptions *tap) +{ + return 0; +} + +int tap_probe_vnet_hdr(int fd) +{ + return 0; +} + +int tap_probe_has_ufo(int fd) +{ + return 0; +} + +int tap_probe_vnet_hdr_len(int fd, int len) +{ + return 0; +} + +void tap_fd_set_vnet_hdr_len(int fd, int len) +{ +} + +void tap_fd_set_offload(int fd, int csum, int tso4, + int tso6, int ecn, int ufo) +{ +} diff --git a/net/tap-bsd.c b/net/tap-bsd.c new file mode 100644 index 0000000000..a3b717dd1c --- /dev/null +++ b/net/tap-bsd.c @@ -0,0 +1,147 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ + +#include "net/tap.h" +#include "qemu-common.h" +#include "sysemu.h" +#include "qemu-error.h" + +#ifdef __NetBSD__ +#include <sys/ioctl.h> +#include <net/if.h> +#include <net/if_tap.h> +#endif + +int tap_open(char *ifname, int ifname_size, int *vnet_hdr, int vnet_hdr_required) +{ + int fd; +#ifdef TAPGIFNAME + struct ifreq ifr; +#else + char *dev; + struct stat s; +#endif + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) + /* if no ifname is given, always start the search from tap0/tun0. */ + int i; + char dname[100]; + + for (i = 0; i < 10; i++) { + if (*ifname) { + snprintf(dname, sizeof dname, "/dev/%s", ifname); + } else { +#if defined(__OpenBSD__) + snprintf(dname, sizeof dname, "/dev/tun%d", i); +#else + snprintf(dname, sizeof dname, "/dev/tap%d", i); +#endif + } + TFR(fd = open(dname, O_RDWR)); + if (fd >= 0) { + break; + } + else if (errno == ENXIO || errno == ENOENT) { + break; + } + if (*ifname) { + break; + } + } + if (fd < 0) { + error_report("warning: could not open %s (%s): no virtual network emulation", + dname, strerror(errno)); + return -1; + } +#else + TFR(fd = open("/dev/tap", O_RDWR)); + if (fd < 0) { + fprintf(stderr, + "warning: could not open /dev/tap: no virtual network emulation: %s\n", + strerror(errno)); + return -1; + } +#endif + +#ifdef TAPGIFNAME + if (ioctl(fd, TAPGIFNAME, (void *)&ifr) < 0) { + fprintf(stderr, "warning: could not get tap name: %s\n", + strerror(errno)); + return -1; + } + pstrcpy(ifname, ifname_size, ifr.ifr_name); +#else + if (fstat(fd, &s) < 0) { + fprintf(stderr, + "warning: could not stat /dev/tap: no virtual network emulation: %s\n", + strerror(errno)); + return -1; + } + dev = devname(s.st_rdev, S_IFCHR); + pstrcpy(ifname, ifname_size, dev); +#endif + + if (*vnet_hdr) { + /* BSD doesn't have IFF_VNET_HDR */ + *vnet_hdr = 0; + + if (vnet_hdr_required && !*vnet_hdr) { + error_report("vnet_hdr=1 requested, but no kernel " + "support for IFF_VNET_HDR available"); + close(fd); + return -1; + } + } + fcntl(fd, F_SETFL, O_NONBLOCK); + return fd; +} + +int tap_set_sndbuf(int fd, const NetdevTapOptions *tap) +{ + return 0; +} + +int tap_probe_vnet_hdr(int fd) +{ + return 0; +} + +int tap_probe_has_ufo(int fd) +{ + return 0; +} + +int tap_probe_vnet_hdr_len(int fd, int len) +{ + return 0; +} + +void tap_fd_set_vnet_hdr_len(int fd, int len) +{ +} + +void tap_fd_set_offload(int fd, int csum, int tso4, + int tso6, int ecn, int ufo) +{ +} diff --git a/net/tap-haiku.c b/net/tap-haiku.c new file mode 100644 index 0000000000..34739d1562 --- /dev/null +++ b/net/tap-haiku.c @@ -0,0 +1,61 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ + +#include "net/tap.h" +#include <stdio.h> + +int tap_open(char *ifname, int ifname_size, int *vnet_hdr, int vnet_hdr_required) +{ + fprintf(stderr, "no tap on Haiku\n"); + return -1; +} + +int tap_set_sndbuf(int fd, const NetdevTapOptions *tap) +{ + return 0; +} + +int tap_probe_vnet_hdr(int fd) +{ + return 0; +} + +int tap_probe_has_ufo(int fd) +{ + return 0; +} + +int tap_probe_vnet_hdr_len(int fd, int len) +{ + return 0; +} + +void tap_fd_set_vnet_hdr_len(int fd, int len) +{ +} + +void tap_fd_set_offload(int fd, int csum, int tso4, + int tso6, int ecn, int ufo) +{ +} diff --git a/net/tap-linux.c b/net/tap-linux.c new file mode 100644 index 0000000000..c6521bec34 --- /dev/null +++ b/net/tap-linux.c @@ -0,0 +1,202 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * Copyright (c) 2009 Red Hat, Inc. + * + * 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. + */ + +#include "net/tap.h" +#include "net/tap-linux.h" + +#include <net/if.h> +#include <sys/ioctl.h> + +#include "sysemu.h" +#include "qemu-common.h" +#include "qemu-error.h" + +#define PATH_NET_TUN "/dev/net/tun" + +int tap_open(char *ifname, int ifname_size, int *vnet_hdr, int vnet_hdr_required) +{ + struct ifreq ifr; + int fd, ret; + + TFR(fd = open(PATH_NET_TUN, O_RDWR)); + if (fd < 0) { + error_report("could not open %s: %m", PATH_NET_TUN); + return -1; + } + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + + if (*vnet_hdr) { + unsigned int features; + + if (ioctl(fd, TUNGETFEATURES, &features) == 0 && + features & IFF_VNET_HDR) { + *vnet_hdr = 1; + ifr.ifr_flags |= IFF_VNET_HDR; + } else { + *vnet_hdr = 0; + } + + if (vnet_hdr_required && !*vnet_hdr) { + error_report("vnet_hdr=1 requested, but no kernel " + "support for IFF_VNET_HDR available"); + close(fd); + return -1; + } + } + + if (ifname[0] != '\0') + pstrcpy(ifr.ifr_name, IFNAMSIZ, ifname); + else + pstrcpy(ifr.ifr_name, IFNAMSIZ, "tap%d"); + ret = ioctl(fd, TUNSETIFF, (void *) &ifr); + if (ret != 0) { + if (ifname[0] != '\0') { + error_report("could not configure %s (%s): %m", PATH_NET_TUN, ifr.ifr_name); + } else { + error_report("could not configure %s: %m", PATH_NET_TUN); + } + close(fd); + return -1; + } + pstrcpy(ifname, ifname_size, ifr.ifr_name); + fcntl(fd, F_SETFL, O_NONBLOCK); + return fd; +} + +/* sndbuf implements a kind of flow control for tap. + * Unfortunately when it's enabled, and packets are sent + * to other guests on the same host, the receiver + * can lock up the transmitter indefinitely. + * + * To avoid packet loss, sndbuf should be set to a value lower than the tx + * queue capacity of any destination network interface. + * Ethernet NICs generally have txqueuelen=1000, so 1Mb is + * a good value, given a 1500 byte MTU. + */ +#define TAP_DEFAULT_SNDBUF 0 + +int tap_set_sndbuf(int fd, const NetdevTapOptions *tap) +{ + int sndbuf; + + sndbuf = !tap->has_sndbuf ? TAP_DEFAULT_SNDBUF : + tap->sndbuf > INT_MAX ? INT_MAX : + tap->sndbuf; + + if (!sndbuf) { + sndbuf = INT_MAX; + } + + if (ioctl(fd, TUNSETSNDBUF, &sndbuf) == -1 && tap->has_sndbuf) { + error_report("TUNSETSNDBUF ioctl failed: %s", strerror(errno)); + return -1; + } + return 0; +} + +int tap_probe_vnet_hdr(int fd) +{ + struct ifreq ifr; + + if (ioctl(fd, TUNGETIFF, &ifr) != 0) { + error_report("TUNGETIFF ioctl() failed: %s", strerror(errno)); + return 0; + } + + return ifr.ifr_flags & IFF_VNET_HDR; +} + +int tap_probe_has_ufo(int fd) +{ + unsigned offload; + + offload = TUN_F_CSUM | TUN_F_UFO; + + if (ioctl(fd, TUNSETOFFLOAD, offload) < 0) + return 0; + + return 1; +} + +/* Verify that we can assign given length */ +int tap_probe_vnet_hdr_len(int fd, int len) +{ + int orig; + if (ioctl(fd, TUNGETVNETHDRSZ, &orig) == -1) { + return 0; + } + if (ioctl(fd, TUNSETVNETHDRSZ, &len) == -1) { + return 0; + } + /* Restore original length: we can't handle failure. */ + if (ioctl(fd, TUNSETVNETHDRSZ, &orig) == -1) { + fprintf(stderr, "TUNGETVNETHDRSZ ioctl() failed: %s. Exiting.\n", + strerror(errno)); + assert(0); + return -errno; + } + return 1; +} + +void tap_fd_set_vnet_hdr_len(int fd, int len) +{ + if (ioctl(fd, TUNSETVNETHDRSZ, &len) == -1) { + fprintf(stderr, "TUNSETVNETHDRSZ ioctl() failed: %s. Exiting.\n", + strerror(errno)); + assert(0); + } +} + +void tap_fd_set_offload(int fd, int csum, int tso4, + int tso6, int ecn, int ufo) +{ + unsigned int offload = 0; + + /* Check if our kernel supports TUNSETOFFLOAD */ + if (ioctl(fd, TUNSETOFFLOAD, 0) != 0 && errno == EINVAL) { + return; + } + + if (csum) { + offload |= TUN_F_CSUM; + if (tso4) + offload |= TUN_F_TSO4; + if (tso6) + offload |= TUN_F_TSO6; + if ((tso4 || tso6) && ecn) + offload |= TUN_F_TSO_ECN; + if (ufo) + offload |= TUN_F_UFO; + } + + if (ioctl(fd, TUNSETOFFLOAD, offload) != 0) { + offload &= ~TUN_F_UFO; + if (ioctl(fd, TUNSETOFFLOAD, offload) != 0) { + fprintf(stderr, "TUNSETOFFLOAD ioctl() failed: %s\n", + strerror(errno)); + } + } +} diff --git a/net/tap-linux.h b/net/tap-linux.h new file mode 100644 index 0000000000..659e98122b --- /dev/null +++ b/net/tap-linux.h @@ -0,0 +1,63 @@ +/* + * Universal TUN/TAP device driver. + * Copyright (C) 1999-2000 Maxim Krasnyansky <max_mk@yahoo.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef QEMU_TAP_H +#define QEMU_TAP_H + +#include <stdint.h> +#ifdef __linux__ + +#include <linux/ioctl.h> + +/* Ioctl defines */ +#define TUNSETIFF _IOW('T', 202, int) +#define TUNGETFEATURES _IOR('T', 207, unsigned int) +#define TUNSETOFFLOAD _IOW('T', 208, unsigned int) +#define TUNGETIFF _IOR('T', 210, unsigned int) +#define TUNSETSNDBUF _IOW('T', 212, int) +#define TUNGETVNETHDRSZ _IOR('T', 215, int) +#define TUNSETVNETHDRSZ _IOW('T', 216, int) + +#endif + +/* TUNSETIFF ifr flags */ +#define IFF_TAP 0x0002 +#define IFF_NO_PI 0x1000 +#define IFF_VNET_HDR 0x4000 + +/* Features for GSO (TUNSETOFFLOAD). */ +#define TUN_F_CSUM 0x01 /* You can hand me unchecksummed packets. */ +#define TUN_F_TSO4 0x02 /* I can handle TSO for IPv4 packets */ +#define TUN_F_TSO6 0x04 /* I can handle TSO for IPv6 packets */ +#define TUN_F_TSO_ECN 0x08 /* I can handle TSO with ECN bits. */ +#define TUN_F_UFO 0x10 /* I can handle UFO packets */ + +struct virtio_net_hdr +{ + uint8_t flags; + uint8_t gso_type; + uint16_t hdr_len; + uint16_t gso_size; + uint16_t csum_start; + uint16_t csum_offset; +}; + +struct virtio_net_hdr_mrg_rxbuf +{ + struct virtio_net_hdr hdr; + uint16_t num_buffers; /* Number of merged rx buffers */ +}; + +#endif /* QEMU_TAP_H */ diff --git a/net/tap-solaris.c b/net/tap-solaris.c new file mode 100644 index 0000000000..5d6ac42f24 --- /dev/null +++ b/net/tap-solaris.c @@ -0,0 +1,227 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ + +#include "net/tap.h" +#include "sysemu.h" + +#include <sys/stat.h> +#include <sys/ethernet.h> +#include <sys/sockio.h> +#include <netinet/arp.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> // must come after ip.h +#include <netinet/udp.h> +#include <netinet/tcp.h> +#include <net/if.h> +#include <syslog.h> +#include <stropts.h> +#include "qemu-error.h" + +ssize_t tap_read_packet(int tapfd, uint8_t *buf, int maxlen) +{ + struct strbuf sbuf; + int f = 0; + + sbuf.maxlen = maxlen; + sbuf.buf = (char *)buf; + + return getmsg(tapfd, NULL, &sbuf, &f) >= 0 ? sbuf.len : -1; +} + +#define TUNNEWPPA (('T'<<16) | 0x0001) +/* + * Allocate TAP device, returns opened fd. + * Stores dev name in the first arg(must be large enough). + */ +static int tap_alloc(char *dev, size_t dev_size) +{ + int tap_fd, if_fd, ppa = -1; + static int ip_fd = 0; + char *ptr; + + static int arp_fd = 0; + int ip_muxid, arp_muxid; + struct strioctl strioc_if, strioc_ppa; + int link_type = I_PLINK; + struct lifreq ifr; + char actual_name[32] = ""; + + memset(&ifr, 0x0, sizeof(ifr)); + + if( *dev ){ + ptr = dev; + while( *ptr && !qemu_isdigit((int)*ptr) ) ptr++; + ppa = atoi(ptr); + } + + /* Check if IP device was opened */ + if( ip_fd ) + close(ip_fd); + + TFR(ip_fd = open("/dev/udp", O_RDWR, 0)); + if (ip_fd < 0) { + syslog(LOG_ERR, "Can't open /dev/ip (actually /dev/udp)"); + return -1; + } + + TFR(tap_fd = open("/dev/tap", O_RDWR, 0)); + if (tap_fd < 0) { + syslog(LOG_ERR, "Can't open /dev/tap"); + return -1; + } + + /* Assign a new PPA and get its unit number. */ + strioc_ppa.ic_cmd = TUNNEWPPA; + strioc_ppa.ic_timout = 0; + strioc_ppa.ic_len = sizeof(ppa); + strioc_ppa.ic_dp = (char *)&ppa; + if ((ppa = ioctl (tap_fd, I_STR, &strioc_ppa)) < 0) + syslog (LOG_ERR, "Can't assign new interface"); + + TFR(if_fd = open("/dev/tap", O_RDWR, 0)); + if (if_fd < 0) { + syslog(LOG_ERR, "Can't open /dev/tap (2)"); + return -1; + } + if(ioctl(if_fd, I_PUSH, "ip") < 0){ + syslog(LOG_ERR, "Can't push IP module"); + return -1; + } + + if (ioctl(if_fd, SIOCGLIFFLAGS, &ifr) < 0) + syslog(LOG_ERR, "Can't get flags\n"); + + snprintf (actual_name, 32, "tap%d", ppa); + pstrcpy(ifr.lifr_name, sizeof(ifr.lifr_name), actual_name); + + ifr.lifr_ppa = ppa; + /* Assign ppa according to the unit number returned by tun device */ + + if (ioctl (if_fd, SIOCSLIFNAME, &ifr) < 0) + syslog (LOG_ERR, "Can't set PPA %d", ppa); + if (ioctl(if_fd, SIOCGLIFFLAGS, &ifr) <0) + syslog (LOG_ERR, "Can't get flags\n"); + /* Push arp module to if_fd */ + if (ioctl (if_fd, I_PUSH, "arp") < 0) + syslog (LOG_ERR, "Can't push ARP module (2)"); + + /* Push arp module to ip_fd */ + if (ioctl (ip_fd, I_POP, NULL) < 0) + syslog (LOG_ERR, "I_POP failed\n"); + if (ioctl (ip_fd, I_PUSH, "arp") < 0) + syslog (LOG_ERR, "Can't push ARP module (3)\n"); + /* Open arp_fd */ + TFR(arp_fd = open ("/dev/tap", O_RDWR, 0)); + if (arp_fd < 0) + syslog (LOG_ERR, "Can't open %s\n", "/dev/tap"); + + /* Set ifname to arp */ + strioc_if.ic_cmd = SIOCSLIFNAME; + strioc_if.ic_timout = 0; + strioc_if.ic_len = sizeof(ifr); + strioc_if.ic_dp = (char *)𝔦 + if (ioctl(arp_fd, I_STR, &strioc_if) < 0){ + syslog (LOG_ERR, "Can't set ifname to arp\n"); + } + + if((ip_muxid = ioctl(ip_fd, I_LINK, if_fd)) < 0){ + syslog(LOG_ERR, "Can't link TAP device to IP"); + return -1; + } + + if ((arp_muxid = ioctl (ip_fd, link_type, arp_fd)) < 0) + syslog (LOG_ERR, "Can't link TAP device to ARP"); + + close (if_fd); + + memset(&ifr, 0x0, sizeof(ifr)); + pstrcpy(ifr.lifr_name, sizeof(ifr.lifr_name), actual_name); + ifr.lifr_ip_muxid = ip_muxid; + ifr.lifr_arp_muxid = arp_muxid; + + if (ioctl (ip_fd, SIOCSLIFMUXID, &ifr) < 0) + { + ioctl (ip_fd, I_PUNLINK , arp_muxid); + ioctl (ip_fd, I_PUNLINK, ip_muxid); + syslog (LOG_ERR, "Can't set multiplexor id"); + } + + snprintf(dev, dev_size, "tap%d", ppa); + return tap_fd; +} + +int tap_open(char *ifname, int ifname_size, int *vnet_hdr, int vnet_hdr_required) +{ + char dev[10]=""; + int fd; + if( (fd = tap_alloc(dev, sizeof(dev))) < 0 ){ + fprintf(stderr, "Cannot allocate TAP device\n"); + return -1; + } + pstrcpy(ifname, ifname_size, dev); + if (*vnet_hdr) { + /* Solaris doesn't have IFF_VNET_HDR */ + *vnet_hdr = 0; + + if (vnet_hdr_required && !*vnet_hdr) { + error_report("vnet_hdr=1 requested, but no kernel " + "support for IFF_VNET_HDR available"); + close(fd); + return -1; + } + } + fcntl(fd, F_SETFL, O_NONBLOCK); + return fd; +} + +int tap_set_sndbuf(int fd, const NetdevTapOptions *tap) +{ + return 0; +} + +int tap_probe_vnet_hdr(int fd) +{ + return 0; +} + +int tap_probe_has_ufo(int fd) +{ + return 0; +} + +int tap_probe_vnet_hdr_len(int fd, int len) +{ + return 0; +} + +void tap_fd_set_vnet_hdr_len(int fd, int len) +{ +} + +void tap_fd_set_offload(int fd, int csum, int tso4, + int tso6, int ecn, int ufo) +{ +} diff --git a/net/tap-win32.c b/net/tap-win32.c new file mode 100644 index 0000000000..d65fcf2479 --- /dev/null +++ b/net/tap-win32.c @@ -0,0 +1,763 @@ +/* + * TAP-Win32 -- A kernel driver to provide virtual tap device functionality + * on Windows. Originally derived from the CIPE-Win32 + * project by Damion K. Wilson, with extensive modifications by + * James Yonan. + * + * All source code which derives from the CIPE-Win32 project is + * Copyright (C) Damion K. Wilson, 2003, and is released under the + * GPL version 2 (see below). + * + * All other source code is Copyright (C) James Yonan, 2003-2004, + * and is released under the GPL version 2 (see below). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, see <http://www.gnu.org/licenses/>. + */ + +#include "net/tap.h" + +#include "qemu-common.h" +#include "net.h" +#include "sysemu.h" +#include "qemu-error.h" +#include <stdio.h> +#include <windows.h> +#include <winioctl.h> + +//============= +// TAP IOCTLs +//============= + +#define TAP_CONTROL_CODE(request,method) \ + CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS) + +#define TAP_IOCTL_GET_MAC TAP_CONTROL_CODE (1, METHOD_BUFFERED) +#define TAP_IOCTL_GET_VERSION TAP_CONTROL_CODE (2, METHOD_BUFFERED) +#define TAP_IOCTL_GET_MTU TAP_CONTROL_CODE (3, METHOD_BUFFERED) +#define TAP_IOCTL_GET_INFO TAP_CONTROL_CODE (4, METHOD_BUFFERED) +#define TAP_IOCTL_CONFIG_POINT_TO_POINT TAP_CONTROL_CODE (5, METHOD_BUFFERED) +#define TAP_IOCTL_SET_MEDIA_STATUS TAP_CONTROL_CODE (6, METHOD_BUFFERED) +#define TAP_IOCTL_CONFIG_DHCP_MASQ TAP_CONTROL_CODE (7, METHOD_BUFFERED) +#define TAP_IOCTL_GET_LOG_LINE TAP_CONTROL_CODE (8, METHOD_BUFFERED) +#define TAP_IOCTL_CONFIG_DHCP_SET_OPT TAP_CONTROL_CODE (9, METHOD_BUFFERED) + +//================= +// Registry keys +//================= + +#define ADAPTER_KEY "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}" + +#define NETWORK_CONNECTIONS_KEY "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}" + +//====================== +// Filesystem prefixes +//====================== + +#define USERMODEDEVICEDIR "\\\\.\\Global\\" +#define TAPSUFFIX ".tap" + + +//====================== +// Compile time configuration +//====================== + +//#define DEBUG_TAP_WIN32 + +#define TUN_ASYNCHRONOUS_WRITES 1 + +#define TUN_BUFFER_SIZE 1560 +#define TUN_MAX_BUFFER_COUNT 32 + +/* + * The data member "buffer" must be the first element in the tun_buffer + * structure. See the function, tap_win32_free_buffer. + */ +typedef struct tun_buffer_s { + unsigned char buffer [TUN_BUFFER_SIZE]; + unsigned long read_size; + struct tun_buffer_s* next; +} tun_buffer_t; + +typedef struct tap_win32_overlapped { + HANDLE handle; + HANDLE read_event; + HANDLE write_event; + HANDLE output_queue_semaphore; + HANDLE free_list_semaphore; + HANDLE tap_semaphore; + CRITICAL_SECTION output_queue_cs; + CRITICAL_SECTION free_list_cs; + OVERLAPPED read_overlapped; + OVERLAPPED write_overlapped; + tun_buffer_t buffers[TUN_MAX_BUFFER_COUNT]; + tun_buffer_t* free_list; + tun_buffer_t* output_queue_front; + tun_buffer_t* output_queue_back; +} tap_win32_overlapped_t; + +static tap_win32_overlapped_t tap_overlapped; + +static tun_buffer_t* get_buffer_from_free_list(tap_win32_overlapped_t* const overlapped) +{ + tun_buffer_t* buffer = NULL; + WaitForSingleObject(overlapped->free_list_semaphore, INFINITE); + EnterCriticalSection(&overlapped->free_list_cs); + buffer = overlapped->free_list; +// assert(buffer != NULL); + overlapped->free_list = buffer->next; + LeaveCriticalSection(&overlapped->free_list_cs); + buffer->next = NULL; + return buffer; +} + +static void put_buffer_on_free_list(tap_win32_overlapped_t* const overlapped, tun_buffer_t* const buffer) +{ + EnterCriticalSection(&overlapped->free_list_cs); + buffer->next = overlapped->free_list; + overlapped->free_list = buffer; + LeaveCriticalSection(&overlapped->free_list_cs); + ReleaseSemaphore(overlapped->free_list_semaphore, 1, NULL); +} + +static tun_buffer_t* get_buffer_from_output_queue(tap_win32_overlapped_t* const overlapped, const int block) +{ + tun_buffer_t* buffer = NULL; + DWORD result, timeout = block ? INFINITE : 0L; + + // Non-blocking call + result = WaitForSingleObject(overlapped->output_queue_semaphore, timeout); + + switch (result) + { + // The semaphore object was signaled. + case WAIT_OBJECT_0: + EnterCriticalSection(&overlapped->output_queue_cs); + + buffer = overlapped->output_queue_front; + overlapped->output_queue_front = buffer->next; + + if(overlapped->output_queue_front == NULL) { + overlapped->output_queue_back = NULL; + } + + LeaveCriticalSection(&overlapped->output_queue_cs); + break; + + // Semaphore was nonsignaled, so a time-out occurred. + case WAIT_TIMEOUT: + // Cannot open another window. + break; + } + + return buffer; +} + +static tun_buffer_t* get_buffer_from_output_queue_immediate (tap_win32_overlapped_t* const overlapped) +{ + return get_buffer_from_output_queue(overlapped, 0); +} + +static void put_buffer_on_output_queue(tap_win32_overlapped_t* const overlapped, tun_buffer_t* const buffer) +{ + EnterCriticalSection(&overlapped->output_queue_cs); + + if(overlapped->output_queue_front == NULL && overlapped->output_queue_back == NULL) { + overlapped->output_queue_front = overlapped->output_queue_back = buffer; + } else { + buffer->next = NULL; + overlapped->output_queue_back->next = buffer; + overlapped->output_queue_back = buffer; + } + + LeaveCriticalSection(&overlapped->output_queue_cs); + + ReleaseSemaphore(overlapped->output_queue_semaphore, 1, NULL); +} + + +static int is_tap_win32_dev(const char *guid) +{ + HKEY netcard_key; + LONG status; + DWORD len; + int i = 0; + + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + ADAPTER_KEY, + 0, + KEY_READ, + &netcard_key); + + if (status != ERROR_SUCCESS) { + return FALSE; + } + + for (;;) { + char enum_name[256]; + char unit_string[256]; + HKEY unit_key; + char component_id_string[] = "ComponentId"; + char component_id[256]; + char net_cfg_instance_id_string[] = "NetCfgInstanceId"; + char net_cfg_instance_id[256]; + DWORD data_type; + + len = sizeof (enum_name); + status = RegEnumKeyEx( + netcard_key, + i, + enum_name, + &len, + NULL, + NULL, + NULL, + NULL); + + if (status == ERROR_NO_MORE_ITEMS) + break; + else if (status != ERROR_SUCCESS) { + return FALSE; + } + + snprintf (unit_string, sizeof(unit_string), "%s\\%s", + ADAPTER_KEY, enum_name); + + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + unit_string, + 0, + KEY_READ, + &unit_key); + + if (status != ERROR_SUCCESS) { + return FALSE; + } else { + len = sizeof (component_id); + status = RegQueryValueEx( + unit_key, + component_id_string, + NULL, + &data_type, + (LPBYTE)component_id, + &len); + + if (!(status != ERROR_SUCCESS || data_type != REG_SZ)) { + len = sizeof (net_cfg_instance_id); + status = RegQueryValueEx( + unit_key, + net_cfg_instance_id_string, + NULL, + &data_type, + (LPBYTE)net_cfg_instance_id, + &len); + + if (status == ERROR_SUCCESS && data_type == REG_SZ) { + if (/* !strcmp (component_id, TAP_COMPONENT_ID) &&*/ + !strcmp (net_cfg_instance_id, guid)) { + RegCloseKey (unit_key); + RegCloseKey (netcard_key); + return TRUE; + } + } + } + RegCloseKey (unit_key); + } + ++i; + } + + RegCloseKey (netcard_key); + return FALSE; +} + +static int get_device_guid( + char *name, + int name_size, + char *actual_name, + int actual_name_size) +{ + LONG status; + HKEY control_net_key; + DWORD len; + int i = 0; + int stop = 0; + + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + NETWORK_CONNECTIONS_KEY, + 0, + KEY_READ, + &control_net_key); + + if (status != ERROR_SUCCESS) { + return -1; + } + + while (!stop) + { + char enum_name[256]; + char connection_string[256]; + HKEY connection_key; + char name_data[256]; + DWORD name_type; + const char name_string[] = "Name"; + + len = sizeof (enum_name); + status = RegEnumKeyEx( + control_net_key, + i, + enum_name, + &len, + NULL, + NULL, + NULL, + NULL); + + if (status == ERROR_NO_MORE_ITEMS) + break; + else if (status != ERROR_SUCCESS) { + return -1; + } + + snprintf(connection_string, + sizeof(connection_string), + "%s\\%s\\Connection", + NETWORK_CONNECTIONS_KEY, enum_name); + + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + connection_string, + 0, + KEY_READ, + &connection_key); + + if (status == ERROR_SUCCESS) { + len = sizeof (name_data); + status = RegQueryValueEx( + connection_key, + name_string, + NULL, + &name_type, + (LPBYTE)name_data, + &len); + + if (status != ERROR_SUCCESS || name_type != REG_SZ) { + return -1; + } + else { + if (is_tap_win32_dev(enum_name)) { + snprintf(name, name_size, "%s", enum_name); + if (actual_name) { + if (strcmp(actual_name, "") != 0) { + if (strcmp(name_data, actual_name) != 0) { + RegCloseKey (connection_key); + ++i; + continue; + } + } + else { + snprintf(actual_name, actual_name_size, "%s", name_data); + } + } + stop = 1; + } + } + + RegCloseKey (connection_key); + } + ++i; + } + + RegCloseKey (control_net_key); + + if (stop == 0) + return -1; + + return 0; +} + +static int tap_win32_set_status(HANDLE handle, int status) +{ + unsigned long len = 0; + + return DeviceIoControl(handle, TAP_IOCTL_SET_MEDIA_STATUS, + &status, sizeof (status), + &status, sizeof (status), &len, NULL); +} + +static void tap_win32_overlapped_init(tap_win32_overlapped_t* const overlapped, const HANDLE handle) +{ + overlapped->handle = handle; + + overlapped->read_event = CreateEvent(NULL, FALSE, FALSE, NULL); + overlapped->write_event = CreateEvent(NULL, FALSE, FALSE, NULL); + + overlapped->read_overlapped.Offset = 0; + overlapped->read_overlapped.OffsetHigh = 0; + overlapped->read_overlapped.hEvent = overlapped->read_event; + + overlapped->write_overlapped.Offset = 0; + overlapped->write_overlapped.OffsetHigh = 0; + overlapped->write_overlapped.hEvent = overlapped->write_event; + + InitializeCriticalSection(&overlapped->output_queue_cs); + InitializeCriticalSection(&overlapped->free_list_cs); + + overlapped->output_queue_semaphore = CreateSemaphore( + NULL, // default security attributes + 0, // initial count + TUN_MAX_BUFFER_COUNT, // maximum count + NULL); // unnamed semaphore + + if(!overlapped->output_queue_semaphore) { + fprintf(stderr, "error creating output queue semaphore!\n"); + } + + overlapped->free_list_semaphore = CreateSemaphore( + NULL, // default security attributes + TUN_MAX_BUFFER_COUNT, // initial count + TUN_MAX_BUFFER_COUNT, // maximum count + NULL); // unnamed semaphore + + if(!overlapped->free_list_semaphore) { + fprintf(stderr, "error creating free list semaphore!\n"); + } + + overlapped->free_list = overlapped->output_queue_front = overlapped->output_queue_back = NULL; + + { + unsigned index; + for(index = 0; index < TUN_MAX_BUFFER_COUNT; index++) { + tun_buffer_t* element = &overlapped->buffers[index]; + element->next = overlapped->free_list; + overlapped->free_list = element; + } + } + /* To count buffers, initially no-signal. */ + overlapped->tap_semaphore = CreateSemaphore(NULL, 0, TUN_MAX_BUFFER_COUNT, NULL); + if(!overlapped->tap_semaphore) + fprintf(stderr, "error creating tap_semaphore.\n"); +} + +static int tap_win32_write(tap_win32_overlapped_t *overlapped, + const void *buffer, unsigned long size) +{ + unsigned long write_size; + BOOL result; + DWORD error; + + result = GetOverlappedResult( overlapped->handle, &overlapped->write_overlapped, + &write_size, FALSE); + + if (!result && GetLastError() == ERROR_IO_INCOMPLETE) + WaitForSingleObject(overlapped->write_event, INFINITE); + + result = WriteFile(overlapped->handle, buffer, size, + &write_size, &overlapped->write_overlapped); + + if (!result) { + switch (error = GetLastError()) + { + case ERROR_IO_PENDING: +#ifndef TUN_ASYNCHRONOUS_WRITES + WaitForSingleObject(overlapped->write_event, INFINITE); +#endif + break; + default: + return -1; + } + } + + return write_size; +} + +static DWORD WINAPI tap_win32_thread_entry(LPVOID param) +{ + tap_win32_overlapped_t *overlapped = (tap_win32_overlapped_t*)param; + unsigned long read_size; + BOOL result; + DWORD dwError; + tun_buffer_t* buffer = get_buffer_from_free_list(overlapped); + + + for (;;) { + result = ReadFile(overlapped->handle, + buffer->buffer, + sizeof(buffer->buffer), + &read_size, + &overlapped->read_overlapped); + if (!result) { + dwError = GetLastError(); + if (dwError == ERROR_IO_PENDING) { + WaitForSingleObject(overlapped->read_event, INFINITE); + result = GetOverlappedResult( overlapped->handle, &overlapped->read_overlapped, + &read_size, FALSE); + if (!result) { +#ifdef DEBUG_TAP_WIN32 + LPVOID lpBuffer; + dwError = GetLastError(); + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) & lpBuffer, 0, NULL ); + fprintf(stderr, "Tap-Win32: Error GetOverlappedResult %d - %s\n", dwError, lpBuffer); + LocalFree( lpBuffer ); +#endif + } + } else { +#ifdef DEBUG_TAP_WIN32 + LPVOID lpBuffer; + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) & lpBuffer, 0, NULL ); + fprintf(stderr, "Tap-Win32: Error ReadFile %d - %s\n", dwError, lpBuffer); + LocalFree( lpBuffer ); +#endif + } + } + + if(read_size > 0) { + buffer->read_size = read_size; + put_buffer_on_output_queue(overlapped, buffer); + ReleaseSemaphore(overlapped->tap_semaphore, 1, NULL); + buffer = get_buffer_from_free_list(overlapped); + } + } + + return 0; +} + +static int tap_win32_read(tap_win32_overlapped_t *overlapped, + uint8_t **pbuf, int max_size) +{ + int size = 0; + + tun_buffer_t* buffer = get_buffer_from_output_queue_immediate(overlapped); + + if(buffer != NULL) { + *pbuf = buffer->buffer; + size = (int)buffer->read_size; + if(size > max_size) { + size = max_size; + } + } + + return size; +} + +static void tap_win32_free_buffer(tap_win32_overlapped_t *overlapped, + uint8_t *pbuf) +{ + tun_buffer_t* buffer = (tun_buffer_t*)pbuf; + put_buffer_on_free_list(overlapped, buffer); +} + +static int tap_win32_open(tap_win32_overlapped_t **phandle, + const char *prefered_name) +{ + char device_path[256]; + char device_guid[0x100]; + int rc; + HANDLE handle; + BOOL bret; + char name_buffer[0x100] = {0, }; + struct { + unsigned long major; + unsigned long minor; + unsigned long debug; + } version; + DWORD version_len; + DWORD idThread; + + if (prefered_name != NULL) + snprintf(name_buffer, sizeof(name_buffer), "%s", prefered_name); + + rc = get_device_guid(device_guid, sizeof(device_guid), name_buffer, sizeof(name_buffer)); + if (rc) + return -1; + + snprintf (device_path, sizeof(device_path), "%s%s%s", + USERMODEDEVICEDIR, + device_guid, + TAPSUFFIX); +#ifndef CONFIG_MARU + handle = CreateFile ( + device_path, + GENERIC_READ | GENERIC_WRITE, + 0, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, + 0 ); +#else + handle = CreateFile ( + g_win32_locale_filename_from_utf8(device_path), + GENERIC_READ | GENERIC_WRITE, + 0, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, + 0 ); + +#endif + if (handle == INVALID_HANDLE_VALUE) { + return -1; + } + + bret = DeviceIoControl(handle, TAP_IOCTL_GET_VERSION, + &version, sizeof (version), + &version, sizeof (version), &version_len, NULL); + + if (bret == FALSE) { + CloseHandle(handle); + return -1; + } + + if (!tap_win32_set_status(handle, TRUE)) { + return -1; + } + + tap_win32_overlapped_init(&tap_overlapped, handle); + + *phandle = &tap_overlapped; + + CreateThread(NULL, 0, tap_win32_thread_entry, + (LPVOID)&tap_overlapped, 0, &idThread); + return 0; +} + +/********************************************/ + + typedef struct TAPState { + NetClientState nc; + tap_win32_overlapped_t *handle; + } TAPState; + +static void tap_cleanup(NetClientState *nc) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + + qemu_del_wait_object(s->handle->tap_semaphore, NULL, NULL); + + /* FIXME: need to kill thread and close file handle: + tap_win32_close(s); + */ +} + +static ssize_t tap_receive(NetClientState *nc, const uint8_t *buf, size_t size) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + + return tap_win32_write(s->handle, buf, size); +} + +static void tap_win32_send(void *opaque) +{ + TAPState *s = opaque; + uint8_t *buf; + int max_size = 4096; + int size; + + size = tap_win32_read(s->handle, &buf, max_size); + if (size > 0) { + qemu_send_packet(&s->nc, buf, size); + tap_win32_free_buffer(s->handle, buf); + } +} + +static NetClientInfo net_tap_win32_info = { + .type = NET_CLIENT_OPTIONS_KIND_TAP, + .size = sizeof(TAPState), + .receive = tap_receive, + .cleanup = tap_cleanup, +}; + +static int tap_win32_init(NetClientState *peer, const char *model, + const char *name, const char *ifname) +{ + NetClientState *nc; + TAPState *s; + tap_win32_overlapped_t *handle; + + if (tap_win32_open(&handle, ifname) < 0) { + printf("tap: Could not open '%s'\n", ifname); + return -1; + } + + nc = qemu_new_net_client(&net_tap_win32_info, peer, model, name); + + s = DO_UPCAST(TAPState, nc, nc); + + snprintf(s->nc.info_str, sizeof(s->nc.info_str), + "tap: ifname=%s", ifname); + + s->handle = handle; + + qemu_add_wait_object(s->handle->tap_semaphore, tap_win32_send, s); + + return 0; +} + +int net_init_tap(const NetClientOptions *opts, const char *name, + NetClientState *peer) +{ + const NetdevTapOptions *tap; + + assert(opts->kind == NET_CLIENT_OPTIONS_KIND_TAP); + tap = opts->tap; + + if (!tap->has_ifname) { + error_report("tap: no interface name"); + return -1; + } + + if (tap_win32_init(peer, "tap", name, tap->ifname) == -1) { + return -1; + } + + return 0; +} + +int tap_has_ufo(NetClientState *nc) +{ + return 0; +} + +int tap_has_vnet_hdr(NetClientState *nc) +{ + return 0; +} + +int tap_probe_vnet_hdr_len(int fd, int len) +{ + return 0; +} + +void tap_fd_set_vnet_hdr_len(int fd, int len) +{ +} + +void tap_using_vnet_hdr(NetClientState *nc, int using_vnet_hdr) +{ +} + +void tap_set_offload(NetClientState *nc, int csum, int tso4, + int tso6, int ecn, int ufo) +{ +} + +struct vhost_net *tap_get_vhost_net(NetClientState *nc) +{ + return NULL; +} diff --git a/net/tap.c b/net/tap.c new file mode 100644 index 0000000000..1971525794 --- /dev/null +++ b/net/tap.c @@ -0,0 +1,716 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * Copyright (c) 2009 Red Hat, Inc. + * + * 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. + */ + +#include "net/tap.h" + +#include "config-host.h" + +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <net/if.h> + +#include "net.h" +#include "monitor.h" +#include "sysemu.h" +#include "qemu-char.h" +#include "qemu-common.h" +#include "qemu-error.h" + +#include "net/tap-linux.h" + +#include "hw/vhost_net.h" + +/* Maximum GSO packet size (64k) plus plenty of room for + * the ethernet and virtio_net headers + */ +#define TAP_BUFSIZE (4096 + 65536) + +typedef struct TAPState { + NetClientState nc; + int fd; + char down_script[1024]; + char down_script_arg[128]; + uint8_t buf[TAP_BUFSIZE]; + unsigned int read_poll : 1; + unsigned int write_poll : 1; + unsigned int using_vnet_hdr : 1; + unsigned int has_ufo: 1; + VHostNetState *vhost_net; + unsigned host_vnet_hdr_len; +} TAPState; + +static int launch_script(const char *setup_script, const char *ifname, int fd); + +static int tap_can_send(void *opaque); +static void tap_send(void *opaque); +static void tap_writable(void *opaque); + +static void tap_update_fd_handler(TAPState *s) +{ + qemu_set_fd_handler2(s->fd, + s->read_poll ? tap_can_send : NULL, + s->read_poll ? tap_send : NULL, + s->write_poll ? tap_writable : NULL, + s); +} + +static void tap_read_poll(TAPState *s, int enable) +{ + s->read_poll = !!enable; + tap_update_fd_handler(s); +} + +static void tap_write_poll(TAPState *s, int enable) +{ + s->write_poll = !!enable; + tap_update_fd_handler(s); +} + +static void tap_writable(void *opaque) +{ + TAPState *s = opaque; + + tap_write_poll(s, 0); + + qemu_flush_queued_packets(&s->nc); +} + +static ssize_t tap_write_packet(TAPState *s, const struct iovec *iov, int iovcnt) +{ + ssize_t len; + + do { + len = writev(s->fd, iov, iovcnt); + } while (len == -1 && errno == EINTR); + + if (len == -1 && errno == EAGAIN) { + tap_write_poll(s, 1); + return 0; + } + + return len; +} + +static ssize_t tap_receive_iov(NetClientState *nc, const struct iovec *iov, + int iovcnt) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + const struct iovec *iovp = iov; + struct iovec iov_copy[iovcnt + 1]; + struct virtio_net_hdr_mrg_rxbuf hdr = { }; + + if (s->host_vnet_hdr_len && !s->using_vnet_hdr) { + iov_copy[0].iov_base = &hdr; + iov_copy[0].iov_len = s->host_vnet_hdr_len; + memcpy(&iov_copy[1], iov, iovcnt * sizeof(*iov)); + iovp = iov_copy; + iovcnt++; + } + + return tap_write_packet(s, iovp, iovcnt); +} + +static ssize_t tap_receive_raw(NetClientState *nc, const uint8_t *buf, size_t size) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + struct iovec iov[2]; + int iovcnt = 0; + struct virtio_net_hdr_mrg_rxbuf hdr = { }; + + if (s->host_vnet_hdr_len) { + iov[iovcnt].iov_base = &hdr; + iov[iovcnt].iov_len = s->host_vnet_hdr_len; + iovcnt++; + } + + iov[iovcnt].iov_base = (char *)buf; + iov[iovcnt].iov_len = size; + iovcnt++; + + return tap_write_packet(s, iov, iovcnt); +} + +static ssize_t tap_receive(NetClientState *nc, const uint8_t *buf, size_t size) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + struct iovec iov[1]; + + if (s->host_vnet_hdr_len && !s->using_vnet_hdr) { + return tap_receive_raw(nc, buf, size); + } + + iov[0].iov_base = (char *)buf; + iov[0].iov_len = size; + + return tap_write_packet(s, iov, 1); +} + +static int tap_can_send(void *opaque) +{ + TAPState *s = opaque; + + return qemu_can_send_packet(&s->nc); +} + +#ifndef __sun__ +ssize_t tap_read_packet(int tapfd, uint8_t *buf, int maxlen) +{ + return read(tapfd, buf, maxlen); +} +#endif + +static void tap_send_completed(NetClientState *nc, ssize_t len) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + tap_read_poll(s, 1); +} + +static void tap_send(void *opaque) +{ + TAPState *s = opaque; + int size; + + do { + uint8_t *buf = s->buf; + + size = tap_read_packet(s->fd, s->buf, sizeof(s->buf)); + if (size <= 0) { + break; + } + + if (s->host_vnet_hdr_len && !s->using_vnet_hdr) { + buf += s->host_vnet_hdr_len; + size -= s->host_vnet_hdr_len; + } + + size = qemu_send_packet_async(&s->nc, buf, size, tap_send_completed); + if (size == 0) { + tap_read_poll(s, 0); + } + } while (size > 0 && qemu_can_send_packet(&s->nc)); +} + +int tap_has_ufo(NetClientState *nc) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + + assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP); + + return s->has_ufo; +} + +int tap_has_vnet_hdr(NetClientState *nc) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + + assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP); + + return !!s->host_vnet_hdr_len; +} + +int tap_has_vnet_hdr_len(NetClientState *nc, int len) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + + assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP); + + return tap_probe_vnet_hdr_len(s->fd, len); +} + +void tap_set_vnet_hdr_len(NetClientState *nc, int len) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + + assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP); + assert(len == sizeof(struct virtio_net_hdr_mrg_rxbuf) || + len == sizeof(struct virtio_net_hdr)); + + tap_fd_set_vnet_hdr_len(s->fd, len); + s->host_vnet_hdr_len = len; +} + +void tap_using_vnet_hdr(NetClientState *nc, int using_vnet_hdr) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + + using_vnet_hdr = using_vnet_hdr != 0; + + assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP); + assert(!!s->host_vnet_hdr_len == using_vnet_hdr); + + s->using_vnet_hdr = using_vnet_hdr; +} + +void tap_set_offload(NetClientState *nc, int csum, int tso4, + int tso6, int ecn, int ufo) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + if (s->fd < 0) { + return; + } + + tap_fd_set_offload(s->fd, csum, tso4, tso6, ecn, ufo); +} + +static void tap_cleanup(NetClientState *nc) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + + if (s->vhost_net) { + vhost_net_cleanup(s->vhost_net); + s->vhost_net = NULL; + } + + qemu_purge_queued_packets(nc); + + if (s->down_script[0]) + launch_script(s->down_script, s->down_script_arg, s->fd); + + tap_read_poll(s, 0); + tap_write_poll(s, 0); + close(s->fd); + s->fd = -1; +} + +static void tap_poll(NetClientState *nc, bool enable) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + tap_read_poll(s, enable); + tap_write_poll(s, enable); +} + +int tap_get_fd(NetClientState *nc) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP); + return s->fd; +} + +/* fd support */ + +static NetClientInfo net_tap_info = { + .type = NET_CLIENT_OPTIONS_KIND_TAP, + .size = sizeof(TAPState), + .receive = tap_receive, + .receive_raw = tap_receive_raw, + .receive_iov = tap_receive_iov, + .poll = tap_poll, + .cleanup = tap_cleanup, +}; + +static TAPState *net_tap_fd_init(NetClientState *peer, + const char *model, + const char *name, + int fd, + int vnet_hdr) +{ + NetClientState *nc; + TAPState *s; + + nc = qemu_new_net_client(&net_tap_info, peer, model, name); + + s = DO_UPCAST(TAPState, nc, nc); + + s->fd = fd; + s->host_vnet_hdr_len = vnet_hdr ? sizeof(struct virtio_net_hdr) : 0; + s->using_vnet_hdr = 0; + s->has_ufo = tap_probe_has_ufo(s->fd); + tap_set_offload(&s->nc, 0, 0, 0, 0, 0); + tap_read_poll(s, 1); + s->vhost_net = NULL; + return s; +} + +static int launch_script(const char *setup_script, const char *ifname, int fd) +{ + int pid, status; + char *args[3]; + char **parg; + + /* try to launch network script */ + pid = fork(); + if (pid == 0) { + int open_max = sysconf(_SC_OPEN_MAX), i; + + for (i = 0; i < open_max; i++) { + if (i != STDIN_FILENO && + i != STDOUT_FILENO && + i != STDERR_FILENO && + i != fd) { + close(i); + } + } + parg = args; + *parg++ = (char *)setup_script; + *parg++ = (char *)ifname; + *parg = NULL; + execv(setup_script, args); + _exit(1); + } else if (pid > 0) { + while (waitpid(pid, &status, 0) != pid) { + /* loop */ + } + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + return 0; + } + } + fprintf(stderr, "%s: could not launch network script\n", setup_script); + return -1; +} + +static int recv_fd(int c) +{ + int fd; + uint8_t msgbuf[CMSG_SPACE(sizeof(fd))]; + struct msghdr msg = { + .msg_control = msgbuf, + .msg_controllen = sizeof(msgbuf), + }; + struct cmsghdr *cmsg; + struct iovec iov; + uint8_t req[1]; + ssize_t len; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + msg.msg_controllen = cmsg->cmsg_len; + + iov.iov_base = req; + iov.iov_len = sizeof(req); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + len = recvmsg(c, &msg, 0); + if (len > 0) { + memcpy(&fd, CMSG_DATA(cmsg), sizeof(fd)); + return fd; + } + + return len; +} + +static int net_bridge_run_helper(const char *helper, const char *bridge) +{ + sigset_t oldmask, mask; + int pid, status; + char *args[5]; + char **parg; + int sv[2]; + + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &mask, &oldmask); + + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) { + return -1; + } + + /* try to launch bridge helper */ + pid = fork(); + if (pid == 0) { + int open_max = sysconf(_SC_OPEN_MAX), i; + char fd_buf[6+10]; + char br_buf[6+IFNAMSIZ] = {0}; + char helper_cmd[PATH_MAX + sizeof(fd_buf) + sizeof(br_buf) + 15]; + + for (i = 0; i < open_max; i++) { + if (i != STDIN_FILENO && + i != STDOUT_FILENO && + i != STDERR_FILENO && + i != sv[1]) { + close(i); + } + } + + snprintf(fd_buf, sizeof(fd_buf), "%s%d", "--fd=", sv[1]); + + if (strrchr(helper, ' ') || strrchr(helper, '\t')) { + /* assume helper is a command */ + + if (strstr(helper, "--br=") == NULL) { + snprintf(br_buf, sizeof(br_buf), "%s%s", "--br=", bridge); + } + + snprintf(helper_cmd, sizeof(helper_cmd), "%s %s %s %s", + helper, "--use-vnet", fd_buf, br_buf); + + parg = args; + *parg++ = (char *)"sh"; + *parg++ = (char *)"-c"; + *parg++ = helper_cmd; + *parg++ = NULL; + + execv("/bin/sh", args); + } else { + /* assume helper is just the executable path name */ + + snprintf(br_buf, sizeof(br_buf), "%s%s", "--br=", bridge); + + parg = args; + *parg++ = (char *)helper; + *parg++ = (char *)"--use-vnet"; + *parg++ = fd_buf; + *parg++ = br_buf; + *parg++ = NULL; + + execv(helper, args); + } + _exit(1); + + } else if (pid > 0) { + int fd; + + close(sv[1]); + + do { + fd = recv_fd(sv[0]); + } while (fd == -1 && errno == EINTR); + + close(sv[0]); + + while (waitpid(pid, &status, 0) != pid) { + /* loop */ + } + sigprocmask(SIG_SETMASK, &oldmask, NULL); + if (fd < 0) { + fprintf(stderr, "failed to recv file descriptor\n"); + return -1; + } + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + return fd; + } + } + fprintf(stderr, "failed to launch bridge helper\n"); + return -1; +} + +int net_init_bridge(const NetClientOptions *opts, const char *name, + NetClientState *peer) +{ + const NetdevBridgeOptions *bridge; + const char *helper, *br; + + TAPState *s; + int fd, vnet_hdr; + + assert(opts->kind == NET_CLIENT_OPTIONS_KIND_BRIDGE); + bridge = opts->bridge; + + helper = bridge->has_helper ? bridge->helper : DEFAULT_BRIDGE_HELPER; + br = bridge->has_br ? bridge->br : DEFAULT_BRIDGE_INTERFACE; + + fd = net_bridge_run_helper(helper, br); + if (fd == -1) { + return -1; + } + + fcntl(fd, F_SETFL, O_NONBLOCK); + + vnet_hdr = tap_probe_vnet_hdr(fd); + + s = net_tap_fd_init(peer, "bridge", name, fd, vnet_hdr); + if (!s) { + close(fd); + return -1; + } + + snprintf(s->nc.info_str, sizeof(s->nc.info_str), "helper=%s,br=%s", helper, + br); + + return 0; +} + +static int net_tap_init(const NetdevTapOptions *tap, int *vnet_hdr, + const char *setup_script, char *ifname, + size_t ifname_sz) +{ + int fd, vnet_hdr_required; + + if (tap->has_ifname) { + pstrcpy(ifname, ifname_sz, tap->ifname); + } else { + assert(ifname_sz > 0); + ifname[0] = '\0'; + } + + if (tap->has_vnet_hdr) { + *vnet_hdr = tap->vnet_hdr; + vnet_hdr_required = *vnet_hdr; + } else { + *vnet_hdr = 1; + vnet_hdr_required = 0; + } + + TFR(fd = tap_open(ifname, ifname_sz, vnet_hdr, vnet_hdr_required)); + if (fd < 0) { + return -1; + } + + if (setup_script && + setup_script[0] != '\0' && + strcmp(setup_script, "no") != 0 && + launch_script(setup_script, ifname, fd)) { + close(fd); + return -1; + } + + return fd; +} + +int net_init_tap(const NetClientOptions *opts, const char *name, + NetClientState *peer) +{ + const NetdevTapOptions *tap; + + int fd, vnet_hdr = 0; + const char *model; + TAPState *s; + + /* for the no-fd, no-helper case */ + const char *script = NULL; /* suppress wrong "uninit'd use" gcc warning */ + char ifname[128]; + + assert(opts->kind == NET_CLIENT_OPTIONS_KIND_TAP); + tap = opts->tap; + + if (tap->has_fd) { + if (tap->has_ifname || tap->has_script || tap->has_downscript || + tap->has_vnet_hdr || tap->has_helper) { + error_report("ifname=, script=, downscript=, vnet_hdr=, " + "and helper= are invalid with fd="); + return -1; + } + + fd = net_handle_fd_param(cur_mon, tap->fd); + if (fd == -1) { + return -1; + } + + fcntl(fd, F_SETFL, O_NONBLOCK); + + vnet_hdr = tap_probe_vnet_hdr(fd); + + model = "tap"; + + } else if (tap->has_helper) { + if (tap->has_ifname || tap->has_script || tap->has_downscript || + tap->has_vnet_hdr) { + error_report("ifname=, script=, downscript=, and vnet_hdr= " + "are invalid with helper="); + return -1; + } + + fd = net_bridge_run_helper(tap->helper, DEFAULT_BRIDGE_INTERFACE); + if (fd == -1) { + return -1; + } + + fcntl(fd, F_SETFL, O_NONBLOCK); + + vnet_hdr = tap_probe_vnet_hdr(fd); + + model = "bridge"; + + } else { + script = tap->has_script ? tap->script : DEFAULT_NETWORK_SCRIPT; + fd = net_tap_init(tap, &vnet_hdr, script, ifname, sizeof ifname); + if (fd == -1) { + return -1; + } + + model = "tap"; + } + + s = net_tap_fd_init(peer, model, name, fd, vnet_hdr); + if (!s) { + close(fd); + return -1; + } + + if (tap_set_sndbuf(s->fd, tap) < 0) { + return -1; + } + + if (tap->has_fd) { + snprintf(s->nc.info_str, sizeof(s->nc.info_str), "fd=%d", fd); + } else if (tap->has_helper) { + snprintf(s->nc.info_str, sizeof(s->nc.info_str), "helper=%s", + tap->helper); + } else { + const char *downscript; + + downscript = tap->has_downscript ? tap->downscript : + DEFAULT_NETWORK_DOWN_SCRIPT; + + snprintf(s->nc.info_str, sizeof(s->nc.info_str), + "ifname=%s,script=%s,downscript=%s", ifname, script, + downscript); + + if (strcmp(downscript, "no") != 0) { + snprintf(s->down_script, sizeof(s->down_script), "%s", downscript); + snprintf(s->down_script_arg, sizeof(s->down_script_arg), "%s", ifname); + } + } + + if (tap->has_vhost ? tap->vhost : + tap->has_vhostfd || (tap->has_vhostforce && tap->vhostforce)) { + int vhostfd; + + if (tap->has_vhostfd) { + vhostfd = net_handle_fd_param(cur_mon, tap->vhostfd); + if (vhostfd == -1) { + return -1; + } + } else { + vhostfd = -1; + } + + s->vhost_net = vhost_net_init(&s->nc, vhostfd, + tap->has_vhostforce && tap->vhostforce); + if (!s->vhost_net) { + error_report("vhost-net requested but could not be initialized"); + return -1; + } + } else if (tap->has_vhostfd) { + error_report("vhostfd= is not valid without vhost"); + return -1; + } + + return 0; +} + +VHostNetState *tap_get_vhost_net(NetClientState *nc) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP); + return s->vhost_net; +} diff --git a/net/tap.h b/net/tap.h new file mode 100644 index 0000000000..0fb018c4b7 --- /dev/null +++ b/net/tap.h @@ -0,0 +1,64 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * Copyright (c) 2009 Red Hat, Inc. + * + * 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. + */ + +#ifndef QEMU_NET_TAP_H +#define QEMU_NET_TAP_H + +#include "qemu-common.h" +#include "qapi-types.h" + +#define DEFAULT_NETWORK_SCRIPT "/etc/qemu-ifup" +#define DEFAULT_NETWORK_DOWN_SCRIPT "/etc/qemu-ifdown" + +int net_init_tap(const NetClientOptions *opts, const char *name, + NetClientState *peer); + +int tap_open(char *ifname, int ifname_size, int *vnet_hdr, int vnet_hdr_required); + +ssize_t tap_read_packet(int tapfd, uint8_t *buf, int maxlen); + +int tap_has_ufo(NetClientState *nc); +int tap_has_vnet_hdr(NetClientState *nc); +int tap_has_vnet_hdr_len(NetClientState *nc, int len); +void tap_using_vnet_hdr(NetClientState *nc, int using_vnet_hdr); +void tap_set_offload(NetClientState *nc, int csum, int tso4, int tso6, int ecn, int ufo); +void tap_set_vnet_hdr_len(NetClientState *nc, int len); + +int tap_set_sndbuf(int fd, const NetdevTapOptions *tap); +int tap_probe_vnet_hdr(int fd); +int tap_probe_vnet_hdr_len(int fd, int len); +int tap_probe_has_ufo(int fd); +void tap_fd_set_offload(int fd, int csum, int tso4, int tso6, int ecn, int ufo); +void tap_fd_set_vnet_hdr_len(int fd, int len); + +int tap_get_fd(NetClientState *nc); + +struct vhost_net; +struct vhost_net *tap_get_vhost_net(NetClientState *nc); + +int net_init_bridge(const NetClientOptions *opts, const char *name, + NetClientState *peer); + +#endif /* QEMU_NET_TAP_H */ diff --git a/net/util.c b/net/util.c new file mode 100644 index 0000000000..1e9afbc1ae --- /dev/null +++ b/net/util.c @@ -0,0 +1,60 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ + +#include "net/util.h" +#include <errno.h> +#include <stdlib.h> + +int net_parse_macaddr(uint8_t *macaddr, const char *p) +{ + int i; + char *last_char; + long int offset; + + errno = 0; + offset = strtol(p, &last_char, 0); + if (errno == 0 && *last_char == '\0' && + offset >= 0 && offset <= 0xFFFFFF) { + macaddr[3] = (offset & 0xFF0000) >> 16; + macaddr[4] = (offset & 0xFF00) >> 8; + macaddr[5] = offset & 0xFF; + return 0; + } + + for (i = 0; i < 6; i++) { + macaddr[i] = strtol(p, (char **)&p, 16); + if (i == 5) { + if (*p != '\0') { + return -1; + } + } else { + if (*p != ':' && *p != '-') { + return -1; + } + p++; + } + } + + return 0; +} diff --git a/net/util.h b/net/util.h new file mode 100644 index 0000000000..10c7da95f0 --- /dev/null +++ b/net/util.h @@ -0,0 +1,32 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ + +#ifndef QEMU_NET_UTIL_H +#define QEMU_NET_UTIL_H + +#include <stdint.h> + +int net_parse_macaddr(uint8_t *macaddr, const char *p); + +#endif /* QEMU_NET_UTIL_H */ diff --git a/net/vde.c b/net/vde.c new file mode 100644 index 0000000000..b91a6c799b --- /dev/null +++ b/net/vde.c @@ -0,0 +1,128 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ +#include "net/vde.h" + +#include "config-host.h" + +#include <libvdeplug.h> + +#include "net.h" +#include "qemu-char.h" +#include "qemu-common.h" +#include "qemu-option.h" + +typedef struct VDEState { + NetClientState nc; + VDECONN *vde; +} VDEState; + +static void vde_to_qemu(void *opaque) +{ + VDEState *s = opaque; + uint8_t buf[4096]; + int size; + + size = vde_recv(s->vde, (char *)buf, sizeof(buf), 0); + if (size > 0) { + qemu_send_packet(&s->nc, buf, size); + } +} + +static ssize_t vde_receive(NetClientState *nc, const uint8_t *buf, size_t size) +{ + VDEState *s = DO_UPCAST(VDEState, nc, nc); + ssize_t ret; + + do { + ret = vde_send(s->vde, (const char *)buf, size, 0); + } while (ret < 0 && errno == EINTR); + + return ret; +} + +static void vde_cleanup(NetClientState *nc) +{ + VDEState *s = DO_UPCAST(VDEState, nc, nc); + qemu_set_fd_handler(vde_datafd(s->vde), NULL, NULL, NULL); + vde_close(s->vde); +} + +static NetClientInfo net_vde_info = { + .type = NET_CLIENT_OPTIONS_KIND_VDE, + .size = sizeof(VDEState), + .receive = vde_receive, + .cleanup = vde_cleanup, +}; + +static int net_vde_init(NetClientState *peer, const char *model, + const char *name, const char *sock, + int port, const char *group, int mode) +{ + NetClientState *nc; + VDEState *s; + VDECONN *vde; + char *init_group = (char *)group; + char *init_sock = (char *)sock; + + struct vde_open_args args = { + .port = port, + .group = init_group, + .mode = mode, + }; + + vde = vde_open(init_sock, (char *)"QEMU", &args); + if (!vde){ + return -1; + } + + nc = qemu_new_net_client(&net_vde_info, peer, model, name); + + snprintf(nc->info_str, sizeof(nc->info_str), "sock=%s,fd=%d", + sock, vde_datafd(vde)); + + s = DO_UPCAST(VDEState, nc, nc); + + s->vde = vde; + + qemu_set_fd_handler(vde_datafd(s->vde), vde_to_qemu, NULL, s); + + return 0; +} + +int net_init_vde(const NetClientOptions *opts, const char *name, + NetClientState *peer) +{ + const NetdevVdeOptions *vde; + + assert(opts->kind == NET_CLIENT_OPTIONS_KIND_VDE); + vde = opts->vde; + + /* missing optional values have been initialized to "all bits zero" */ + if (net_vde_init(peer, "vde", name, vde->sock, vde->port, vde->group, + vde->has_mode ? vde->mode : 0700) == -1) { + return -1; + } + + return 0; +} diff --git a/net/vde.h b/net/vde.h new file mode 100644 index 0000000000..6ce6698937 --- /dev/null +++ b/net/vde.h @@ -0,0 +1,37 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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. + */ +#ifndef QEMU_NET_VDE_H +#define QEMU_NET_VDE_H + +#include "qemu-common.h" +#include "qapi-types.h" + +#ifdef CONFIG_VDE + +int net_init_vde(const NetClientOptions *opts, const char *name, + NetClientState *peer); + +#endif /* CONFIG_VDE */ + +#endif /* QEMU_NET_VDE_H */ |