|  | /* | 
|  | * 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/monitor.h" | 
|  | #include "net/net.h" | 
|  | #include "clients.h" | 
|  | #include "hub.h" | 
|  | #include "qemu/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 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | 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) { | 
|  | monitor_printf(mon, " \\ %s", port->nc.name); | 
|  | if (port->nc.peer) { | 
|  | monitor_printf(mon, ": "); | 
|  | print_net_client(mon, port->nc.peer); | 
|  | } else { | 
|  | monitor_printf(mon, "\n"); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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; | 
|  |  | 
|  | 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: | 
|  | case NET_CLIENT_OPTIONS_KIND_VHOST_USER: | 
|  | 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); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bool net_hub_flush(NetClientState *nc) | 
|  | { | 
|  | NetHubPort *port; | 
|  | NetHubPort *source_port = DO_UPCAST(NetHubPort, nc, nc); | 
|  | int ret = 0; | 
|  |  | 
|  | QLIST_FOREACH(port, &source_port->hub->ports, next) { | 
|  | if (port != source_port) { | 
|  | ret += qemu_net_queue_flush(port->nc.incoming_queue); | 
|  | } | 
|  | } | 
|  | return ret ? true : false; | 
|  | } |