diff options
Diffstat (limited to 'net/net.c')
-rw-r--r-- | net/net.c | 1002 |
1 files changed, 670 insertions, 332 deletions
@@ -27,6 +27,7 @@ #include "net/net.h" #include "clients.h" #include "hub.h" +#include "hw/qdev-properties.h" #include "net/slirp.h" #include "net/eth.h" #include "util.h" @@ -41,16 +42,20 @@ #include "qemu/sockets.h" #include "qemu/cutils.h" #include "qemu/config-file.h" -#include "hw/qdev.h" +#include "qemu/ctype.h" +#include "qemu/id.h" #include "qemu/iov.h" +#include "qemu/qemu-print.h" #include "qemu/main-loop.h" #include "qemu/option.h" +#include "qemu/keyval.h" #include "qapi/error.h" #include "qapi/opts-visitor.h" -#include "sysemu/sysemu.h" -#include "sysemu/qtest.h" +#include "sysemu/runstate.h" +#include "net/colo-compare.h" #include "net/filter.h" #include "qapi/string-output-visitor.h" +#include "qapi/qobject-input-visitor.h" /* Net bridge is currently not supported for W32. */ #if !defined(_WIN32) @@ -58,73 +63,83 @@ #endif static VMChangeStateEntry *net_change_state_entry; -static QTAILQ_HEAD(, NetClientState) net_clients; +NetClientStateList net_clients; + +typedef struct NetdevQueueEntry { + Netdev *nd; + Location loc; + QSIMPLEQ_ENTRY(NetdevQueueEntry) entry; +} NetdevQueueEntry; + +typedef QSIMPLEQ_HEAD(, NetdevQueueEntry) NetdevQueue; + +static NetdevQueue nd_queue = QSIMPLEQ_HEAD_INITIALIZER(nd_queue); + +static GHashTable *nic_model_help; + +static int nb_nics; +static NICInfo nd_table[MAX_NICS]; /***********************************************************/ /* network device redirectors */ -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; -} - -int parse_host_port(struct sockaddr_in *saddr, const char *str, - Error **errp) +int convert_host_port(struct sockaddr_in *saddr, const char *host, + const char *port, Error **errp) { - char buf[512]; struct hostent *he; - const char *p, *r; - int port; + const char *r; + long p; + + memset(saddr, 0, sizeof(*saddr)); - p = str; - if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) { - error_setg(errp, "host address '%s' doesn't contain ':' " - "separating host from port", str); - return -1; - } saddr->sin_family = AF_INET; - if (buf[0] == '\0') { + if (host[0] == '\0') { saddr->sin_addr.s_addr = 0; } else { - if (qemu_isdigit(buf[0])) { - if (!inet_aton(buf, &saddr->sin_addr)) { + if (qemu_isdigit(host[0])) { + if (!inet_aton(host, &saddr->sin_addr)) { error_setg(errp, "host address '%s' is not a valid " - "IPv4 address", buf); + "IPv4 address", host); return -1; } } else { - he = gethostbyname(buf); + he = gethostbyname(host); if (he == NULL) { - error_setg(errp, "can't resolve host address '%s'", buf); - return - 1; + error_setg(errp, "can't resolve host address '%s'", host); + return -1; } saddr->sin_addr = *(struct in_addr *)he->h_addr; } } - port = strtol(p, (char **)&r, 0); - if (r == p) { - error_setg(errp, "port number '%s' is invalid", p); + if (qemu_strtol(port, &r, 0, &p) != 0) { + error_setg(errp, "port number '%s' is invalid", port); return -1; } - saddr->sin_port = htons(port); + saddr->sin_port = htons(p); return 0; } +int parse_host_port(struct sockaddr_in *saddr, const char *str, + Error **errp) +{ + gchar **substrings; + int ret; + + substrings = g_strsplit(str, ":", 2); + if (!substrings || !substrings[0] || !substrings[1]) { + error_setg(errp, "host address '%s' doesn't contain ':' " + "separating host from port", str); + ret = -1; + goto out; + } + + ret = convert_host_port(saddr, substrings[0], substrings[1], errp); + +out: + g_strfreev(substrings); + return ret; +} + char *qemu_mac_strdup_printf(const uint8_t *macaddr) { return g_strdup_printf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", @@ -132,13 +147,20 @@ char *qemu_mac_strdup_printf(const uint8_t *macaddr) macaddr[3], macaddr[4], macaddr[5]); } +void qemu_set_info_str(NetClientState *nc, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsnprintf(nc->info_str, sizeof(nc->info_str), fmt, ap); + va_end(ap); +} + void qemu_format_nic_info_str(NetClientState *nc, uint8_t macaddr[6]) { - snprintf(nc->info_str, sizeof(nc->info_str), - "model=%s,macaddr=%02x:%02x:%02x:%02x:%02x:%02x", - nc->model, - macaddr[0], macaddr[1], macaddr[2], - macaddr[3], macaddr[4], macaddr[5]); + qemu_set_info_str(nc, "model=%s,macaddr=%02x:%02x:%02x:%02x:%02x:%02x", + nc->model, macaddr[0], macaddr[1], macaddr[2], + macaddr[3], macaddr[4], macaddr[5]); } static int mac_table[256] = {0}; @@ -242,7 +264,8 @@ static void qemu_net_client_setup(NetClientState *nc, NetClientState *peer, const char *model, const char *name, - NetClientDestructor *destructor) + NetClientDestructor *destructor, + bool is_datapath) { nc->info = info; nc->model = g_strdup(model); @@ -261,6 +284,7 @@ static void qemu_net_client_setup(NetClientState *nc, nc->incoming_queue = qemu_new_net_queue(qemu_deliver_packet_iov, nc); nc->destructor = destructor; + nc->is_datapath = is_datapath; QTAILQ_INIT(&nc->filters); } @@ -275,7 +299,23 @@ NetClientState *qemu_new_net_client(NetClientInfo *info, nc = g_malloc0(info->size); qemu_net_client_setup(nc, info, peer, model, name, - qemu_net_client_destructor); + qemu_net_client_destructor, true); + + return nc; +} + +NetClientState *qemu_new_net_control_client(NetClientInfo *info, + NetClientState *peer, + const char *model, + const char *name) +{ + NetClientState *nc; + + assert(info->size >= sizeof(NetClientState)); + + nc = g_malloc0(info->size); + qemu_net_client_setup(nc, info, peer, model, name, + qemu_net_client_destructor, false); return nc; } @@ -284,6 +324,7 @@ NICState *qemu_new_nic(NetClientInfo *info, NICConf *conf, const char *model, const char *name, + MemReentrancyGuard *reentrancy_guard, void *opaque) { NetClientState **peers = conf->peers.ncs; @@ -296,11 +337,12 @@ NICState *qemu_new_nic(NetClientInfo *info, nic = g_malloc0(info->size + sizeof(NetClientState) * queues); nic->ncs = (void *)nic + info->size; nic->conf = conf; + nic->reentrancy_guard = reentrancy_guard, nic->opaque = opaque; for (i = 0; i < queues; i++) { qemu_net_client_setup(&nic->ncs[i], info, peers[i], model, name, - NULL); + NULL, true); nic->ncs[i].queue_index = i; } @@ -331,6 +373,13 @@ void *qemu_get_nic_opaque(NetClientState *nc) return nic->opaque; } +NetClientState *qemu_get_peer(NetClientState *nc, int queue_index) +{ + assert(nc != NULL); + NetClientState *ncs = nc + queue_index; + return ncs->peer; +} + static void qemu_cleanup_net_client(NetClientState *nc) { QTAILQ_REMOVE(&net_clients, nc, next); @@ -410,10 +459,14 @@ void qemu_del_nic(NICState *nic) qemu_macaddr_set_free(&nic->conf->macaddr); - /* If this is a peer NIC and peer has already been deleted, free it now. */ - if (nic->peer_deleted) { - for (i = 0; i < queues; i++) { - qemu_free_net_client(qemu_get_subqueue(nic, i)->peer); + for (i = 0; i < queues; i++) { + NetClientState *nc = qemu_get_subqueue(nic, i); + /* If this is a peer NIC and peer has already been deleted, free it now. */ + if (nic->peer_deleted) { + qemu_free_net_client(nc->peer); + } else if (nc->peer) { + /* if there are RX packets pending, complete them */ + qemu_purge_queued_packets(nc->peer); } } @@ -449,6 +502,15 @@ bool qemu_has_ufo(NetClientState *nc) return nc->info->has_ufo(nc); } +bool qemu_has_uso(NetClientState *nc) +{ + if (!nc || !nc->info->has_uso) { + return false; + } + + return nc->info->has_uso(nc); +} + bool qemu_has_vnet_hdr(NetClientState *nc) { if (!nc || !nc->info->has_vnet_hdr) { @@ -467,6 +529,15 @@ bool qemu_has_vnet_hdr_len(NetClientState *nc, int len) return nc->info->has_vnet_hdr_len(nc, len); } +bool qemu_get_using_vnet_hdr(NetClientState *nc) +{ + if (!nc || !nc->info->get_using_vnet_hdr) { + return false; + } + + return nc->info->get_using_vnet_hdr(nc); +} + void qemu_using_vnet_hdr(NetClientState *nc, bool enable) { if (!nc || !nc->info->using_vnet_hdr) { @@ -477,13 +548,22 @@ void qemu_using_vnet_hdr(NetClientState *nc, bool enable) } void qemu_set_offload(NetClientState *nc, int csum, int tso4, int tso6, - int ecn, int ufo) + int ecn, int ufo, int uso4, int uso6) { if (!nc || !nc->info->set_offload) { return; } - nc->info->set_offload(nc, csum, tso4, tso6, ecn, ufo); + nc->info->set_offload(nc, csum, tso4, tso6, ecn, ufo, uso4, uso6); +} + +int qemu_get_vnet_hdr_len(NetClientState *nc) +{ + if (!nc || !nc->info->get_vnet_hdr_len) { + return 0; + } + + return nc->info->get_vnet_hdr_len(nc); } void qemu_set_vnet_hdr_len(NetClientState *nc, int len) @@ -498,7 +578,7 @@ void qemu_set_vnet_hdr_len(NetClientState *nc, int len) int qemu_set_vnet_le(NetClientState *nc, bool is_le) { -#ifdef HOST_WORDS_BIGENDIAN +#if HOST_BIG_ENDIAN if (!nc || !nc->info->set_vnet_le) { return -ENOSYS; } @@ -511,7 +591,7 @@ int qemu_set_vnet_le(NetClientState *nc, bool is_le) int qemu_set_vnet_be(NetClientState *nc, bool is_be) { -#ifdef HOST_WORDS_BIGENDIAN +#if HOST_BIG_ENDIAN return 0; #else if (!nc || !nc->info->set_vnet_be) { @@ -522,6 +602,17 @@ int qemu_set_vnet_be(NetClientState *nc, bool is_be) #endif } +int qemu_can_receive_packet(NetClientState *nc) +{ + if (nc->receive_disabled) { + return 0; + } else if (nc->info->can_receive && + !nc->info->can_receive(nc)) { + return 0; + } + return 1; +} + int qemu_can_send_packet(NetClientState *sender) { int vm_running = runstate_is_running(); @@ -534,13 +625,7 @@ int qemu_can_send_packet(NetClientState *sender) return 1; } - if (sender->peer->receive_disabled) { - return 0; - } else if (sender->peer->info->can_receive && - !sender->peer->info->can_receive(sender->peer)) { - return 0; - } - return 1; + return qemu_can_receive_packet(sender->peer); } static ssize_t filter_receive_iov(NetClientState *nc, @@ -616,7 +701,7 @@ void qemu_flush_or_purge_queued_packets(NetClientState *nc, bool purge) qemu_notify_event(); } else if (purge) { /* Unable to empty the queue, purge remaining packets */ - qemu_net_queue_purge(nc->incoming_queue, nc); + qemu_net_queue_purge(nc->incoming_queue, nc->peer); } } @@ -635,7 +720,7 @@ static ssize_t qemu_send_packet_async_with_flags(NetClientState *sender, #ifdef DEBUG_NET printf("qemu_send_packet_async:\n"); - qemu_hexdump((const char *)buf, stdout, "net", size); + qemu_hexdump(stdout, "net", buf, size); #endif if (sender->link_down || !sender->peer) { @@ -668,9 +753,28 @@ ssize_t qemu_send_packet_async(NetClientState *sender, buf, size, sent_cb); } -void qemu_send_packet(NetClientState *nc, const uint8_t *buf, int size) +ssize_t qemu_send_packet(NetClientState *nc, const uint8_t *buf, int size) { - qemu_send_packet_async(nc, buf, size, NULL); + return qemu_send_packet_async(nc, buf, size, NULL); +} + +ssize_t qemu_receive_packet(NetClientState *nc, const uint8_t *buf, int size) +{ + if (!qemu_can_receive_packet(nc)) { + return 0; + } + + return qemu_net_queue_receive(nc->incoming_queue, buf, size); +} + +ssize_t qemu_receive_packet_iov(NetClientState *nc, const struct iovec *iov, + int iovcnt) +{ + if (!qemu_can_receive_packet(nc)) { + return 0; + } + + return qemu_net_queue_receive_iov(nc->incoming_queue, iov, iovcnt); } ssize_t qemu_send_packet_raw(NetClientState *nc, const uint8_t *buf, int size) @@ -716,6 +820,7 @@ static ssize_t qemu_deliver_packet_iov(NetClientState *sender, int iovcnt, void *opaque) { + MemReentrancyGuard *owned_reentrancy_guard; NetClientState *nc = opaque; int ret; @@ -728,12 +833,24 @@ static ssize_t qemu_deliver_packet_iov(NetClientState *sender, return 0; } + if (nc->info->type != NET_CLIENT_DRIVER_NIC || + qemu_get_nic(nc)->reentrancy_guard->engaged_in_io) { + owned_reentrancy_guard = NULL; + } else { + owned_reentrancy_guard = qemu_get_nic(nc)->reentrancy_guard; + owned_reentrancy_guard->engaged_in_io = true; + } + if (nc->info->receive_iov && !(flags & QEMU_NET_PACKET_FLAG_RAW)) { ret = nc->info->receive_iov(nc, iov, iovcnt); } else { ret = nc_sendv_compat(nc, iov, iovcnt, flags); } + if (owned_reentrancy_guard) { + owned_reentrancy_guard->engaged_in_io = false; + } + if (ret == 0) { nc->receive_disabled = 1; } @@ -829,48 +946,38 @@ static int nic_get_free_idx(void) return -1; } -int qemu_show_nic_models(const char *arg, const char *const *models) -{ - int i; - - if (!arg || !is_help_option(arg)) { - return 0; - } - - fprintf(stderr, "qemu: Supported NIC models: "); - for (i = 0 ; models[i]; i++) - fprintf(stderr, "%s%c", models[i], models[i+1] ? ',' : '\n'); - return 1; -} - -void qemu_check_nic_model(NICInfo *nd, const char *model) -{ - const char *models[2]; - - models[0] = model; - models[1] = NULL; - - if (qemu_show_nic_models(nd->model, models)) - exit(0); - if (qemu_find_nic_model(nd, models, model) < 0) - exit(1); -} - -int qemu_find_nic_model(NICInfo *nd, const char * const *models, - const char *default_model) -{ - int i; - - if (!nd->model) - nd->model = g_strdup(default_model); - - for (i = 0 ; models[i]; i++) { - if (strcmp(nd->model, models[i]) == 0) - return i; +GPtrArray *qemu_get_nic_models(const char *device_type) +{ + GPtrArray *nic_models = g_ptr_array_new(); + GSList *list = object_class_get_list_sorted(device_type, false); + + while (list) { + DeviceClass *dc = OBJECT_CLASS_CHECK(DeviceClass, list->data, + TYPE_DEVICE); + GSList *next; + if (test_bit(DEVICE_CATEGORY_NETWORK, dc->categories) && + dc->user_creatable) { + const char *name = object_class_get_name(list->data); + /* + * A network device might also be something else than a NIC, see + * e.g. the "rocker" device. Thus we have to look for the "netdev" + * property, too. Unfortunately, some devices like virtio-net only + * create this property during instance_init, so we have to create + * a temporary instance here to be able to check it. + */ + Object *obj = object_new_with_class(OBJECT_CLASS(dc)); + if (object_property_find(obj, "netdev")) { + g_ptr_array_add(nic_models, (gpointer)name); + } + object_unref(obj); + } + next = list->next; + g_slist_free_1(list); + list = next; } + g_ptr_array_add(nic_models, NULL); - error_report("Unsupported NIC model: %s", nd->model); - return -1; + return nic_models; } static int net_init_nic(const Netdev *netdev, const char *name, @@ -893,7 +1000,7 @@ static int net_init_nic(const Netdev *netdev, const char *name, memset(nd, 0, sizeof(*nd)); - if (nic->has_netdev) { + if (nic->netdev) { nd->netdev = qemu_find_netdev(nic->netdev); if (!nd->netdev) { error_setg(errp, "netdev '%s' not found", nic->netdev); @@ -904,19 +1011,19 @@ static int net_init_nic(const Netdev *netdev, const char *name, nd->netdev = peer; } nd->name = g_strdup(name); - if (nic->has_model) { + if (nic->model) { nd->model = g_strdup(nic->model); } - if (nic->has_addr) { + if (nic->addr) { nd->devaddr = g_strdup(nic->addr); } - if (nic->has_macaddr && + if (nic->macaddr && net_parse_macaddr(nd->macaddr.a, nic->macaddr) < 0) { error_setg(errp, "invalid syntax for ethernet address"); return -1; } - if (nic->has_macaddr && + if (nic->macaddr && is_multicast_ether_addr(nd->macaddr.a)) { error_setg(errp, "NIC cannot have multicast MAC address (odd 1st byte)"); @@ -940,6 +1047,192 @@ static int net_init_nic(const Netdev *netdev, const char *name, return idx; } +static gboolean add_nic_result(gpointer key, gpointer value, gpointer user_data) +{ + GPtrArray *results = user_data; + GPtrArray *alias_list = value; + const char *model = key; + char *result; + + if (!alias_list) { + result = g_strdup(model); + } else { + GString *result_str = g_string_new(model); + int i; + + g_string_append(result_str, " (aka "); + for (i = 0; i < alias_list->len; i++) { + if (i) { + g_string_append(result_str, ", "); + } + g_string_append(result_str, alias_list->pdata[i]); + } + g_string_append(result_str, ")"); + result = result_str->str; + g_string_free(result_str, false); + g_ptr_array_unref(alias_list); + } + g_ptr_array_add(results, result); + return true; +} + +static int model_cmp(char **a, char **b) +{ + return strcmp(*a, *b); +} + +static void show_nic_models(void) +{ + GPtrArray *results = g_ptr_array_new(); + int i; + + g_hash_table_foreach_remove(nic_model_help, add_nic_result, results); + g_ptr_array_sort(results, (GCompareFunc)model_cmp); + + printf("Available NIC models for this configuration:\n"); + for (i = 0 ; i < results->len; i++) { + printf("%s\n", (char *)results->pdata[i]); + } + g_hash_table_unref(nic_model_help); + nic_model_help = NULL; +} + +static void add_nic_model_help(const char *model, const char *alias) +{ + GPtrArray *alias_list = NULL; + + if (g_hash_table_lookup_extended(nic_model_help, model, NULL, + (gpointer *)&alias_list)) { + /* Already exists, no alias to add: return */ + if (!alias) { + return; + } + if (alias_list) { + /* Check if this alias is already in the list. Add if not. */ + if (!g_ptr_array_find_with_equal_func(alias_list, alias, + g_str_equal, NULL)) { + g_ptr_array_add(alias_list, g_strdup(alias)); + } + return; + } + } + /* Either this model wasn't in the list already, or a first alias added */ + if (alias) { + alias_list = g_ptr_array_new(); + g_ptr_array_set_free_func(alias_list, g_free); + g_ptr_array_add(alias_list, g_strdup(alias)); + } + g_hash_table_replace(nic_model_help, g_strdup(model), alias_list); +} + +NICInfo *qemu_find_nic_info(const char *typename, bool match_default, + const char *alias) +{ + NICInfo *nd; + int i; + + if (nic_model_help) { + add_nic_model_help(typename, alias); + } + + for (i = 0; i < nb_nics; i++) { + nd = &nd_table[i]; + + if (!nd->used || nd->instantiated) { + continue; + } + + if ((match_default && !nd->model) || !g_strcmp0(nd->model, typename) + || (alias && !g_strcmp0(nd->model, alias))) { + return nd; + } + } + return NULL; +} + + +/* "I have created a device. Please configure it if you can" */ +bool qemu_configure_nic_device(DeviceState *dev, bool match_default, + const char *alias) +{ + NICInfo *nd = qemu_find_nic_info(object_get_typename(OBJECT(dev)), + match_default, alias); + + if (nd) { + qdev_set_nic_properties(dev, nd); + return true; + } + return false; +} + +/* "Please create a device, if you have a configuration for it" */ +DeviceState *qemu_create_nic_device(const char *typename, bool match_default, + const char *alias) +{ + NICInfo *nd = qemu_find_nic_info(typename, match_default, alias); + DeviceState *dev; + + if (!nd) { + return NULL; + } + + dev = qdev_new(typename); + qdev_set_nic_properties(dev, nd); + return dev; +} + +void qemu_create_nic_bus_devices(BusState *bus, const char *parent_type, + const char *default_model, + const char *alias, const char *alias_target) +{ + GPtrArray *nic_models = qemu_get_nic_models(parent_type); + const char *model; + DeviceState *dev; + NICInfo *nd; + int i; + + if (nic_model_help) { + if (alias_target) { + add_nic_model_help(alias_target, alias); + } + for (i = 0; i < nic_models->len - 1; i++) { + add_nic_model_help(nic_models->pdata[i], NULL); + } + } + + /* Drop the NULL terminator which would make g_str_equal() unhappy */ + nic_models->len--; + + for (i = 0; i < nb_nics; i++) { + nd = &nd_table[i]; + + if (!nd->used || nd->instantiated) { + continue; + } + + model = nd->model ? nd->model : default_model; + if (!model) { + continue; + } + + /* Each bus type is allowed *one* substitution */ + if (g_str_equal(model, alias)) { + model = alias_target; + } + + if (!g_ptr_array_find_with_equal_func(nic_models, model, + g_str_equal, NULL)) { + /* This NIC does not live on this bus. */ + continue; + } + + dev = qdev_new(model); + qdev_set_nic_properties(dev, nd); + qdev_realize_and_unref(dev, bus, &error_fatal); + } + + g_ptr_array_free(nic_models, true); +} static int (* const net_client_init_fun[NET_CLIENT_DRIVER__MAX])( const Netdev *netdev, @@ -951,128 +1244,104 @@ static int (* const net_client_init_fun[NET_CLIENT_DRIVER__MAX])( #endif [NET_CLIENT_DRIVER_TAP] = net_init_tap, [NET_CLIENT_DRIVER_SOCKET] = net_init_socket, + [NET_CLIENT_DRIVER_STREAM] = net_init_stream, + [NET_CLIENT_DRIVER_DGRAM] = net_init_dgram, #ifdef CONFIG_VDE [NET_CLIENT_DRIVER_VDE] = net_init_vde, #endif #ifdef CONFIG_NETMAP [NET_CLIENT_DRIVER_NETMAP] = net_init_netmap, #endif +#ifdef CONFIG_AF_XDP + [NET_CLIENT_DRIVER_AF_XDP] = net_init_af_xdp, +#endif #ifdef CONFIG_NET_BRIDGE [NET_CLIENT_DRIVER_BRIDGE] = net_init_bridge, #endif [NET_CLIENT_DRIVER_HUBPORT] = net_init_hubport, -#ifdef CONFIG_VHOST_NET_USED +#ifdef CONFIG_VHOST_NET_USER [NET_CLIENT_DRIVER_VHOST_USER] = net_init_vhost_user, #endif +#ifdef CONFIG_VHOST_NET_VDPA + [NET_CLIENT_DRIVER_VHOST_VDPA] = net_init_vhost_vdpa, +#endif #ifdef CONFIG_L2TPV3 [NET_CLIENT_DRIVER_L2TPV3] = net_init_l2tpv3, #endif +#ifdef CONFIG_VMNET + [NET_CLIENT_DRIVER_VMNET_HOST] = net_init_vmnet_host, + [NET_CLIENT_DRIVER_VMNET_SHARED] = net_init_vmnet_shared, + [NET_CLIENT_DRIVER_VMNET_BRIDGED] = net_init_vmnet_bridged, +#endif /* CONFIG_VMNET */ }; -static int net_client_init1(const void *object, bool is_netdev, Error **errp) +static int net_client_init1(const Netdev *netdev, bool is_netdev, Error **errp) { - Netdev legacy = {0}; - const Netdev *netdev; - const char *name; NetClientState *peer = NULL; + NetClientState *nc; if (is_netdev) { - netdev = object; - name = netdev->id; - if (netdev->type == NET_CLIENT_DRIVER_NIC || !net_client_init_fun[netdev->type]) { - error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "type", - "a netdev backend type"); + error_setg(errp, "network backend '%s' is not compiled into this binary", + NetClientDriver_str(netdev->type)); return -1; } } else { - const NetLegacy *net = object; - const NetLegacyOptions *opts = net->opts; - legacy.id = net->id; - netdev = &legacy; - /* missing optional values have been initialized to "all bits zero" */ - name = net->has_id ? net->id : net->name; - - if (net->has_name) { - warn_report("The 'name' parameter is deprecated, use 'id' instead"); - } - - /* Map the old options to the new flat type */ - switch (opts->type) { - case NET_LEGACY_OPTIONS_TYPE_NONE: + if (netdev->type == NET_CLIENT_DRIVER_NONE) { return 0; /* nothing to do */ - case NET_LEGACY_OPTIONS_TYPE_NIC: - legacy.type = NET_CLIENT_DRIVER_NIC; - legacy.u.nic = opts->u.nic; - break; - case NET_LEGACY_OPTIONS_TYPE_USER: - legacy.type = NET_CLIENT_DRIVER_USER; - legacy.u.user = opts->u.user; - break; - case NET_LEGACY_OPTIONS_TYPE_TAP: - legacy.type = NET_CLIENT_DRIVER_TAP; - legacy.u.tap = opts->u.tap; - break; - case NET_LEGACY_OPTIONS_TYPE_L2TPV3: - legacy.type = NET_CLIENT_DRIVER_L2TPV3; - legacy.u.l2tpv3 = opts->u.l2tpv3; - break; - case NET_LEGACY_OPTIONS_TYPE_SOCKET: - legacy.type = NET_CLIENT_DRIVER_SOCKET; - legacy.u.socket = opts->u.socket; - break; - case NET_LEGACY_OPTIONS_TYPE_VDE: - legacy.type = NET_CLIENT_DRIVER_VDE; - legacy.u.vde = opts->u.vde; - break; - case NET_LEGACY_OPTIONS_TYPE_BRIDGE: - legacy.type = NET_CLIENT_DRIVER_BRIDGE; - legacy.u.bridge = opts->u.bridge; - break; - case NET_LEGACY_OPTIONS_TYPE_NETMAP: - legacy.type = NET_CLIENT_DRIVER_NETMAP; - legacy.u.netmap = opts->u.netmap; - break; - case NET_LEGACY_OPTIONS_TYPE_VHOST_USER: - legacy.type = NET_CLIENT_DRIVER_VHOST_USER; - legacy.u.vhost_user = opts->u.vhost_user; - break; - default: - abort(); + } + if (netdev->type == NET_CLIENT_DRIVER_HUBPORT) { + error_setg(errp, "network backend '%s' is only supported with -netdev/-nic", + NetClientDriver_str(netdev->type)); + return -1; } if (!net_client_init_fun[netdev->type]) { - error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "type", - "a net backend type (maybe it is not compiled " - "into this binary)"); + error_setg(errp, "network backend '%s' is not compiled into this binary", + NetClientDriver_str(netdev->type)); return -1; } /* Do not add to a hub if it's a nic with a netdev= parameter. */ if (netdev->type != NET_CLIENT_DRIVER_NIC || - !opts->u.nic.has_netdev) { + !netdev->u.nic.netdev) { peer = net_hub_add_port(0, NULL, NULL); } } - if (net_client_init_fun[netdev->type](netdev, name, peer, errp) < 0) { + nc = qemu_find_netdev(netdev->id); + if (nc) { + error_setg(errp, "Duplicate ID '%s'", netdev->id); + return -1; + } + + if (net_client_init_fun[netdev->type](netdev, netdev->id, peer, errp) < 0) { /* FIXME drop when all init functions store an Error */ if (errp && !*errp) { - error_setg(errp, QERR_DEVICE_INIT_FAILED, + error_setg(errp, "Device '%s' could not be initialized", NetClientDriver_str(netdev->type)); } return -1; } + + if (is_netdev) { + nc = qemu_find_netdev(netdev->id); + assert(nc); + nc->is_netdev = true; + } + return 0; } -static void show_netdevs(void) +void show_netdevs(void) { int idx; const char *available_netdevs[] = { "socket", + "stream", + "dgram", "hubport", "tap", #ifdef CONFIG_SLIRP @@ -1090,77 +1359,79 @@ static void show_netdevs(void) #ifdef CONFIG_NETMAP "netmap", #endif +#ifdef CONFIG_AF_XDP + "af-xdp", +#endif #ifdef CONFIG_POSIX "vhost-user", #endif +#ifdef CONFIG_VHOST_VDPA + "vhost-vdpa", +#endif +#ifdef CONFIG_VMNET + "vmnet-host", + "vmnet-shared", + "vmnet-bridged", +#endif }; - printf("Available netdev backend types:\n"); + qemu_printf("Available netdev backend types:\n"); for (idx = 0; idx < ARRAY_SIZE(available_netdevs); idx++) { - puts(available_netdevs[idx]); + qemu_printf("%s\n", available_netdevs[idx]); } } static int net_client_init(QemuOpts *opts, bool is_netdev, Error **errp) { - void *object = NULL; - Error *err = NULL; + gchar **substrings = NULL; + Netdev *object = NULL; int ret = -1; Visitor *v = opts_visitor_new(opts); - const char *type = qemu_opt_get(opts, "type"); + /* Parse convenience option format ipv6-net=fec0::0[/64] */ + const char *ip6_net = qemu_opt_get(opts, "ipv6-net"); - if (is_netdev && type && is_help_option(type)) { - show_netdevs(); - exit(0); - } else { - /* Parse convenience option format ip6-net=fec0::0[/64] */ - const char *ip6_net = qemu_opt_get(opts, "ipv6-net"); + if (ip6_net) { + char *prefix_addr; + unsigned long prefix_len = 64; /* Default 64bit prefix length. */ - if (ip6_net) { - char buf[strlen(ip6_net) + 1]; - - if (get_str_sep(buf, sizeof(buf), &ip6_net, '/') < 0) { - /* Default 64bit prefix length. */ - qemu_opt_set(opts, "ipv6-prefix", ip6_net, &error_abort); - qemu_opt_set_number(opts, "ipv6-prefixlen", 64, &error_abort); - } else { - /* User-specified prefix length. */ - unsigned long len; - int err; + substrings = g_strsplit(ip6_net, "/", 2); + if (!substrings || !substrings[0]) { + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "ipv6-net", + "a valid IPv6 prefix"); + goto out; + } - qemu_opt_set(opts, "ipv6-prefix", buf, &error_abort); - err = qemu_strtoul(ip6_net, NULL, 10, &len); + prefix_addr = substrings[0]; - if (err) { - error_setg(errp, QERR_INVALID_PARAMETER_VALUE, - "ipv6-prefix", "a number"); - } else { - qemu_opt_set_number(opts, "ipv6-prefixlen", len, - &error_abort); - } - } - qemu_opt_unset(opts, "ipv6-net"); + /* Handle user-specified prefix length. */ + if (substrings[1] && + qemu_strtoul(substrings[1], NULL, 10, &prefix_len)) + { + error_setg(errp, + "parameter 'ipv6-net' expects a number after '/'"); + goto out; } - } - if (is_netdev) { - visit_type_Netdev(v, NULL, (Netdev **)&object, &err); - } else { - visit_type_NetLegacy(v, NULL, (NetLegacy **)&object, &err); + qemu_opt_set(opts, "ipv6-prefix", prefix_addr, &error_abort); + qemu_opt_set_number(opts, "ipv6-prefixlen", prefix_len, + &error_abort); + qemu_opt_unset(opts, "ipv6-net"); } - if (!err) { - ret = net_client_init1(object, is_netdev, &err); + /* Create an ID for -net if the user did not specify one */ + if (!is_netdev && !qemu_opts_id(opts)) { + qemu_opts_set_id(opts, id_generate(ID_NET)); } - if (is_netdev) { - qapi_free_Netdev(object); - } else { - qapi_free_NetLegacy(object); + if (visit_type_Netdev(v, NULL, &object, errp)) { + ret = net_client_init1(object, is_netdev, errp); } - error_propagate(errp, err); + qapi_free_Netdev(object); + +out: + g_strfreev(substrings); visit_free(v); return ret; } @@ -1170,30 +1441,14 @@ void netdev_add(QemuOpts *opts, Error **errp) net_client_init(opts, true, errp); } -void qmp_netdev_add(QDict *qdict, QObject **ret, Error **errp) +void qmp_netdev_add(Netdev *netdev, Error **errp) { - Error *local_err = NULL; - QemuOptsList *opts_list; - QemuOpts *opts; - - opts_list = qemu_find_opts_err("netdev", &local_err); - if (local_err) { - goto out; - } - - opts = qemu_opts_from_qdict(opts_list, qdict, &local_err); - if (local_err) { - goto out; - } - - netdev_add(opts, &local_err); - if (local_err) { - qemu_opts_del(opts); - goto out; + if (!id_wellformed(netdev->id)) { + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "id", "an identifier"); + return; } -out: - error_propagate(errp, local_err); + net_client_init1(netdev, true, errp); } void qmp_netdev_del(const char *id, Error **errp) @@ -1208,14 +1463,22 @@ void qmp_netdev_del(const char *id, Error **errp) return; } - opts = qemu_opts_find(qemu_find_opts_err("netdev", NULL), id); - if (!opts) { + if (!nc->is_netdev) { error_setg(errp, "Device '%s' is not a netdev", id); return; } qemu_del_net_client(nc); - qemu_opts_del(opts); + + /* + * Wart: we need to delete the QemuOpts associated with netdevs + * created via CLI or HMP, to avoid bogus "Duplicate ID" errors in + * HMP netdev_add. + */ + opts = qemu_opts_find(qemu_find_opts("netdev"), id); + if (opts) { + qemu_opts_del(opts); + } } static void netfilter_print_info(Monitor *mon, NetFilterState *nf) @@ -1232,7 +1495,7 @@ static void netfilter_print_info(Monitor *mon, NetFilterState *nf) continue; } v = string_output_visitor_new(false, &str); - object_property_get(OBJECT(nf), v, prop->name, NULL); + object_property_get(OBJECT(nf), prop->name, v, NULL); visit_complete(v, &str); visit_free(v); monitor_printf(mon, ",%s=%s", prop->name, str); @@ -1253,33 +1516,30 @@ void print_net_client(Monitor *mon, NetClientState *nc) monitor_printf(mon, "filters:\n"); } QTAILQ_FOREACH(nf, &nc->filters, next) { - char *path = object_get_canonical_path_component(OBJECT(nf)); - - monitor_printf(mon, " - %s: type=%s", path, + monitor_printf(mon, " - %s: type=%s", + object_get_canonical_path_component(OBJECT(nf)), object_get_typename(OBJECT(nf))); netfilter_print_info(mon, nf); - g_free(path); } } -RxFilterInfoList *qmp_query_rx_filter(bool has_name, const char *name, - Error **errp) +RxFilterInfoList *qmp_query_rx_filter(const char *name, Error **errp) { NetClientState *nc; - RxFilterInfoList *filter_list = NULL, *last_entry = NULL; + RxFilterInfoList *filter_list = NULL, **tail = &filter_list; QTAILQ_FOREACH(nc, &net_clients, next) { - RxFilterInfoList *entry; RxFilterInfo *info; - if (has_name && strcmp(nc->name, name) != 0) { + if (name && strcmp(nc->name, name) != 0) { continue; } /* only query rx-filter information of NIC */ if (nc->info->type != NET_CLIENT_DRIVER_NIC) { - if (has_name) { + if (name) { error_setg(errp, "net client(%s) isn't a NIC", name); + assert(!filter_list); return NULL; } continue; @@ -1293,59 +1553,26 @@ RxFilterInfoList *qmp_query_rx_filter(bool has_name, const char *name, if (nc->info->query_rx_filter) { info = nc->info->query_rx_filter(nc); - entry = g_malloc0(sizeof(*entry)); - entry->value = info; - - if (!filter_list) { - filter_list = entry; - } else { - last_entry->next = entry; - } - last_entry = entry; - } else if (has_name) { + QAPI_LIST_APPEND(tail, info); + } else if (name) { error_setg(errp, "net client(%s) doesn't support" " rx-filter querying", name); + assert(!filter_list); return NULL; } - if (has_name) { + if (name) { break; } } - if (filter_list == NULL && has_name) { + if (filter_list == NULL && name) { error_setg(errp, "invalid net client name: %s", name); } return filter_list; } -void hmp_info_network(Monitor *mon, const QDict *qdict) -{ - NetClientState *nc, *peer; - NetClientDriver type; - - net_hub_info(mon); - - QTAILQ_FOREACH(nc, &net_clients, next) { - peer = nc->peer; - type = nc->info->type; - - /* Skip if already printed in hub info */ - if (net_hub_id_for_client(nc, NULL) == 0) { - continue; - } - - if (!peer || type == NET_CLIENT_DRIVER_NIC) { - print_net_client(mon, nc); - } /* else it's a netdev connected to a NIC, printed with the NIC */ - if (peer && type == NET_CLIENT_DRIVER_NIC) { - monitor_printf(mon, " \\ "); - print_net_client(mon, peer); - } - } -} - void colo_notify_filters_event(int event, Error **errp) { NetClientState *nc; @@ -1410,7 +1637,7 @@ void qmp_set_link(const char *name, bool up, Error **errp) } } -static void net_vm_change_state_handler(void *opaque, int running, +static void net_vm_change_state_handler(void *opaque, bool running, RunState state) { NetClientState *nc; @@ -1433,15 +1660,34 @@ static void net_vm_change_state_handler(void *opaque, int running, void net_cleanup(void) { - NetClientState *nc; - - /* We may del multiple entries during qemu_del_net_client(), - * so QTAILQ_FOREACH_SAFE() is also not safe here. + NetClientState *nc, **p = &QTAILQ_FIRST(&net_clients); + + /*cleanup colo compare module for COLO*/ + colo_compare_cleanup(); + + /* + * Walk the net_clients list and remove the netdevs but *not* any + * NET_CLIENT_DRIVER_NIC entries. The latter are owned by the device + * model which created them, and in some cases (e.g. xen-net-device) + * the device itself may do cleanup at exit and will be upset if we + * just delete its NIC from underneath it. + * + * Since qemu_del_net_client() may delete multiple entries, using + * QTAILQ_FOREACH_SAFE() is not safe here. The only safe pointer + * to keep as a bookmark is a NET_CLIENT_DRIVER_NIC entry, so keep + * 'p' pointing to either the head of the list, or the 'next' field + * of the latest NET_CLIENT_DRIVER_NIC, and operate on *p as we walk + * the list. + * + * The 'nc' variable isn't part of the list traversal; it's purely + * for convenience as too much '(*p)->' has a tendency to make the + * readers' eyes bleed. */ - while (!QTAILQ_EMPTY(&net_clients)) { - nc = QTAILQ_FIRST(&net_clients); + while (*p) { + nc = *p; if (nc->info->type == NET_CLIENT_DRIVER_NIC) { - qemu_del_nic(qemu_get_nic(nc)); + /* Skip NET_CLIENT_DRIVER_NIC entries */ + p = &QTAILQ_NEXT(nc, next); } else { qemu_del_net_client(nc); } @@ -1455,6 +1701,10 @@ void net_check_clients(void) NetClientState *nc; int i; + if (nic_model_help) { + show_nic_models(); + exit(0); + } net_hub_check_clients(); QTAILQ_FOREACH(nc, &net_clients, next) { @@ -1488,6 +1738,12 @@ static int net_init_client(void *dummy, QemuOpts *opts, Error **errp) static int net_init_netdev(void *dummy, QemuOpts *opts, Error **errp) { + const char *type = qemu_opt_get(opts, "type"); + + if (type && is_help_option(type)) { + show_netdevs(); + exit(0); + } return net_client_init(opts, true, errp); } @@ -1500,8 +1756,23 @@ static int net_param_nic(void *dummy, QemuOpts *opts, Error **errp) const char *type; type = qemu_opt_get(opts, "type"); - if (type && g_str_equal(type, "none")) { - return 0; /* Nothing to do, default_net is cleared in vl.c */ + if (type) { + if (g_str_equal(type, "none")) { + return 0; /* Nothing to do, default_net is cleared in vl.c */ + } + if (is_help_option(type)) { + GPtrArray *nic_models = qemu_get_nic_models(TYPE_DEVICE); + int i; + show_netdevs(); + printf("\n"); + printf("Available NIC models " + "(use -nic model=help for a filtered list):\n"); + for (i = 0 ; nic_models->pdata[i]; i++) { + printf("%s\n", (char *)nic_models->pdata[i]); + } + g_ptr_array_free(nic_models, true); + exit(0); + } } idx = nic_get_free_idx(); @@ -1518,10 +1789,16 @@ static int net_param_nic(void *dummy, QemuOpts *opts, Error **errp) memset(ni, 0, sizeof(*ni)); ni->model = qemu_opt_get_del(opts, "model"); + if (!nic_model_help && !g_strcmp0(ni->model, "help")) { + nic_model_help = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + return 0; + } + /* Create an ID if the user did not specify one */ nd_id = g_strdup(qemu_opts_id(opts)); if (!nd_id) { - nd_id = g_strdup_printf("__org.qemu.nic%i\n", idx); + nd_id = id_generate(ID_NET); qemu_opts_set_id(opts, nd_id); } @@ -1554,36 +1831,97 @@ out: return ret; } -int net_init_clients(Error **errp) +static void netdev_init_modern(void) +{ + while (!QSIMPLEQ_EMPTY(&nd_queue)) { + NetdevQueueEntry *nd = QSIMPLEQ_FIRST(&nd_queue); + + QSIMPLEQ_REMOVE_HEAD(&nd_queue, entry); + loc_push_restore(&nd->loc); + net_client_init1(nd->nd, true, &error_fatal); + loc_pop(&nd->loc); + qapi_free_Netdev(nd->nd); + g_free(nd); + } +} + +void net_init_clients(void) { net_change_state_entry = qemu_add_vm_change_state_handler(net_vm_change_state_handler, NULL); QTAILQ_INIT(&net_clients); - if (qemu_opts_foreach(qemu_find_opts("netdev"), - net_init_netdev, NULL, errp)) { - return -1; - } + netdev_init_modern(); - if (qemu_opts_foreach(qemu_find_opts("nic"), net_param_nic, NULL, errp)) { - return -1; - } + qemu_opts_foreach(qemu_find_opts("netdev"), net_init_netdev, NULL, + &error_fatal); - if (qemu_opts_foreach(qemu_find_opts("net"), net_init_client, NULL, errp)) { - return -1; - } + qemu_opts_foreach(qemu_find_opts("nic"), net_param_nic, NULL, + &error_fatal); - return 0; + qemu_opts_foreach(qemu_find_opts("net"), net_init_client, NULL, + &error_fatal); } -int net_client_parse(QemuOptsList *opts_list, const char *optarg) +/* + * Does this -netdev argument use modern rather than traditional syntax? + * Modern syntax is to be parsed with netdev_parse_modern(). + * Traditional syntax is to be parsed with net_client_parse(). + */ +bool netdev_is_modern(const char *optstr) { - if (!qemu_opts_parse_noisily(opts_list, optarg, true)) { - return -1; + QemuOpts *opts; + bool is_modern; + const char *type; + static QemuOptsList dummy_opts = { + .name = "netdev", + .implied_opt_name = "type", + .head = QTAILQ_HEAD_INITIALIZER(dummy_opts.head), + .desc = { { } }, + }; + + if (optstr[0] == '{') { + /* This is JSON, which means it's modern syntax */ + return true; } - return 0; + opts = qemu_opts_create(&dummy_opts, NULL, false, &error_abort); + qemu_opts_do_parse(opts, optstr, dummy_opts.implied_opt_name, + &error_abort); + type = qemu_opt_get(opts, "type"); + is_modern = !g_strcmp0(type, "stream") || !g_strcmp0(type, "dgram"); + + qemu_opts_reset(&dummy_opts); + + return is_modern; +} + +/* + * netdev_parse_modern() uses modern, more expressive syntax than + * net_client_parse(), but supports only the -netdev option. + * netdev_parse_modern() appends to @nd_queue, whereas net_client_parse() + * appends to @qemu_netdev_opts. + */ +void netdev_parse_modern(const char *optstr) +{ + Visitor *v; + NetdevQueueEntry *nd; + + v = qobject_input_visitor_new_str(optstr, "type", &error_fatal); + nd = g_new(NetdevQueueEntry, 1); + visit_type_Netdev(v, NULL, &nd->nd, &error_fatal); + visit_free(v); + loc_save(&nd->loc); + + QSIMPLEQ_INSERT_TAIL(&nd_queue, nd, entry); +} + +void net_client_parse(QemuOptsList *opts_list, const char *optstr) +{ + if (!qemu_opts_parse_noisily(opts_list, optstr, true)) { + exit(1); + } } /* From FreeBSD */ |