-----BEGIN PGP SIGNATURE-----
iQEzBAABCAAdFiEEIV1G9IJGaJ7HfzVi7wSWWzmNYhEFAmh0lXsACgkQ7wSWWzmN YhGvVwf+OxTtnr84VdsEckqNVuzVkMHk3PAuSlxpvfjHXnwwo5Efto9lA4h4BUSX As9sYpF3qXZdh95QYB/49CvVdizsI/KW1wPEx4ryVqCi7kcdOrzNB/MMMXBrrJE+ 86xtc2a53CHHcctUIvkBr/GVzhay/gm6VHjnPEB/B0Tv+rTKpIBr/nJzVlG+8uX9 O/XRI0aqnCPlsWDQFR2TbyE4TSSmTw5oXru0I12tPfxt2ed6b+izKubHmqgeLCyH ne+qEy2ds40eBZ4YMDDIsxYKY8RlWIdUY0Dnz6wSjC00BNo5yLu7cirL0Ozd6AsI pK5eqQGZGGQIGV/KD+M7WwKWVltBJg== =rS9w -----END PGP SIGNATURE----- Merge tag 'net-pull-request' of https://github.com/jasowang/qemu into staging # -----BEGIN PGP SIGNATURE----- # # iQEzBAABCAAdFiEEIV1G9IJGaJ7HfzVi7wSWWzmNYhEFAmh0lXsACgkQ7wSWWzmN # YhGvVwf+OxTtnr84VdsEckqNVuzVkMHk3PAuSlxpvfjHXnwwo5Efto9lA4h4BUSX # As9sYpF3qXZdh95QYB/49CvVdizsI/KW1wPEx4ryVqCi7kcdOrzNB/MMMXBrrJE+ # 86xtc2a53CHHcctUIvkBr/GVzhay/gm6VHjnPEB/B0Tv+rTKpIBr/nJzVlG+8uX9 # O/XRI0aqnCPlsWDQFR2TbyE4TSSmTw5oXru0I12tPfxt2ed6b+izKubHmqgeLCyH # ne+qEy2ds40eBZ4YMDDIsxYKY8RlWIdUY0Dnz6wSjC00BNo5yLu7cirL0Ozd6AsI # pK5eqQGZGGQIGV/KD+M7WwKWVltBJg== # =rS9w # -----END PGP SIGNATURE----- # gpg: Signature made Mon 14 Jul 2025 01:28:27 EDT # gpg: using RSA key 215D46F48246689EC77F3562EF04965B398D6211 # gpg: Good signature from "Jason Wang (Jason Wang on RedHat) <jasowang@redhat.com>" [full] # Primary key fingerprint: 215D 46F4 8246 689E C77F 3562 EF04 965B 398D 6211 * tag 'net-pull-request' of https://github.com/jasowang/qemu: net/passt: Implement vhost-user backend support net: Add passt network backend net: Add is_vhost_user flag to vhost_net struct net: Allow network backends to advertise max TX queue size net: Add save_acked_features callback to vhost_net net: Add get_acked_features callback to VhostNetOptions net: Consolidate vhost feature bits into vhost_net structure net: Add get_vhost_net callback to NetClientInfo vhost_net: Rename vhost_set_vring_enable() for clarity net: Define net_client_set_link() net: Refactor stream logic for reuse in '-net passt' virtio-net: Add queues for RSS during migration net: fix buffer overflow in af_xdp_umem_create() Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
commit
6fae7ce148
32 changed files with 1584 additions and 444 deletions
|
|
@ -85,13 +85,59 @@ passt doesn't require any capability or privilege. passt has
|
|||
better performance than ``-net user``, full IPv6 support and better security
|
||||
as it's a daemon that is not executed in QEMU context.
|
||||
|
||||
passt can be connected to QEMU either by using a socket
|
||||
(``-netdev stream``) or using the vhost-user interface (``-netdev vhost-user``).
|
||||
passt_ can be used in the same way as the user backend (using ``-net passt``,
|
||||
``-netdev passt`` or ``-nic passt``) or it can be launched manually and
|
||||
connected to QEMU either by using a socket (``-netdev stream``) or by using
|
||||
the vhost-user interface (``-netdev vhost-user``).
|
||||
|
||||
Using ``-netdev stream`` or ``-netdev vhost-user`` will allow the user to
|
||||
enable functionalities not available through the passt backend interface
|
||||
(like migration).
|
||||
|
||||
See `passt(1)`_ for more details on passt.
|
||||
|
||||
.. _passt: https://passt.top/
|
||||
.. _passt(1): https://passt.top/builds/latest/web/passt.1.html
|
||||
|
||||
To use the passt backend interface
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
There is no need to start the daemon as QEMU will do it for you.
|
||||
|
||||
By default, passt will be started in the socket-based mode.
|
||||
|
||||
.. parsed-literal::
|
||||
|qemu_system| [...OPTIONS...] -nic passt
|
||||
|
||||
(qemu) info network
|
||||
e1000e.0: index=0,type=nic,model=e1000e,macaddr=52:54:00:12:34:56
|
||||
\ #net071: index=0,type=passt,stream,connected to pid 24846
|
||||
|
||||
.. parsed-literal::
|
||||
|qemu_system| [...OPTIONS...] -net nic -net passt,tcp-ports=10001,udp-ports=10001
|
||||
|
||||
(qemu) info network
|
||||
hub 0
|
||||
\ hub0port1: #net136: index=0,type=passt,stream,connected to pid 25204
|
||||
\ hub0port0: e1000e.0: index=0,type=nic,model=e1000e,macaddr=52:54:00:12:34:56
|
||||
|
||||
.. parsed-literal::
|
||||
|qemu_system| [...OPTIONS...] -netdev passt,id=netdev0 -device virtio-net,mac=9a:2b:2c:2d:2e:2f,id=virtio0,netdev=netdev0
|
||||
|
||||
(qemu) info network
|
||||
virtio0: index=0,type=nic,model=virtio-net-pci,macaddr=9a:2b:2c:2d:2e:2f
|
||||
\ netdev0: index=0,type=passt,stream,connected to pid 25428
|
||||
|
||||
To use the vhost-based interface, add the ``vhost-user=on`` parameter and
|
||||
select the virtio-net device:
|
||||
|
||||
.. parsed-literal::
|
||||
|qemu_system| [...OPTIONS...] -nic passt,model=virtio,vhost-user=on
|
||||
|
||||
(qemu) info network
|
||||
virtio-net-pci.0: index=0,type=nic,model=virtio-net-pci,macaddr=52:54:00:12:34:56
|
||||
\ #net006: index=0,type=passt,vhost-user,connected to pid 25731
|
||||
|
||||
To use socket based passt interface:
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
|||
|
|
@ -1287,6 +1287,9 @@ ERST
|
|||
.name = "netdev_add",
|
||||
.args_type = "netdev:O",
|
||||
.params = "[user|tap|socket|stream|dgram|vde|bridge|hubport|netmap|vhost-user"
|
||||
#ifdef CONFIG_PASST
|
||||
"|passt"
|
||||
#endif
|
||||
#ifdef CONFIG_AF_XDP
|
||||
"|af-xdp"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
#include "qemu/osdep.h"
|
||||
#include "net/net.h"
|
||||
#include "net/tap.h"
|
||||
#include "net/vhost-user.h"
|
||||
|
||||
#include "hw/virtio/virtio-net.h"
|
||||
#include "net/vhost_net.h"
|
||||
|
|
@ -101,7 +100,7 @@ VHostNetState *get_vhost_net(NetClientState *nc)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int vhost_set_vring_enable(NetClientState *nc, int enable)
|
||||
int vhost_net_set_vring_enable(NetClientState *nc, int enable)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
#include "qemu/osdep.h"
|
||||
#include "net/net.h"
|
||||
#include "net/tap.h"
|
||||
#include "net/vhost-user.h"
|
||||
#include "net/vhost-vdpa.h"
|
||||
|
||||
#include "standard-headers/linux/vhost_types.h"
|
||||
|
|
@ -36,94 +35,9 @@
|
|||
#include "hw/virtio/virtio-bus.h"
|
||||
#include "linux-headers/linux/vhost.h"
|
||||
|
||||
|
||||
/* Features supported by host kernel. */
|
||||
static const int kernel_feature_bits[] = {
|
||||
VIRTIO_F_NOTIFY_ON_EMPTY,
|
||||
VIRTIO_RING_F_INDIRECT_DESC,
|
||||
VIRTIO_RING_F_EVENT_IDX,
|
||||
VIRTIO_NET_F_MRG_RXBUF,
|
||||
VIRTIO_F_VERSION_1,
|
||||
VIRTIO_NET_F_MTU,
|
||||
VIRTIO_F_IOMMU_PLATFORM,
|
||||
VIRTIO_F_RING_PACKED,
|
||||
VIRTIO_F_RING_RESET,
|
||||
VIRTIO_F_IN_ORDER,
|
||||
VIRTIO_F_NOTIFICATION_DATA,
|
||||
VIRTIO_NET_F_RSC_EXT,
|
||||
VIRTIO_NET_F_HASH_REPORT,
|
||||
VHOST_INVALID_FEATURE_BIT
|
||||
};
|
||||
|
||||
/* Features supported by others. */
|
||||
static const int user_feature_bits[] = {
|
||||
VIRTIO_F_NOTIFY_ON_EMPTY,
|
||||
VIRTIO_F_NOTIFICATION_DATA,
|
||||
VIRTIO_RING_F_INDIRECT_DESC,
|
||||
VIRTIO_RING_F_EVENT_IDX,
|
||||
|
||||
VIRTIO_F_ANY_LAYOUT,
|
||||
VIRTIO_F_VERSION_1,
|
||||
VIRTIO_NET_F_CSUM,
|
||||
VIRTIO_NET_F_GUEST_CSUM,
|
||||
VIRTIO_NET_F_GSO,
|
||||
VIRTIO_NET_F_GUEST_TSO4,
|
||||
VIRTIO_NET_F_GUEST_TSO6,
|
||||
VIRTIO_NET_F_GUEST_ECN,
|
||||
VIRTIO_NET_F_GUEST_UFO,
|
||||
VIRTIO_NET_F_HOST_TSO4,
|
||||
VIRTIO_NET_F_HOST_TSO6,
|
||||
VIRTIO_NET_F_HOST_ECN,
|
||||
VIRTIO_NET_F_HOST_UFO,
|
||||
VIRTIO_NET_F_MRG_RXBUF,
|
||||
VIRTIO_NET_F_MTU,
|
||||
VIRTIO_F_IOMMU_PLATFORM,
|
||||
VIRTIO_F_RING_PACKED,
|
||||
VIRTIO_F_RING_RESET,
|
||||
VIRTIO_F_IN_ORDER,
|
||||
VIRTIO_NET_F_RSS,
|
||||
VIRTIO_NET_F_RSC_EXT,
|
||||
VIRTIO_NET_F_HASH_REPORT,
|
||||
VIRTIO_NET_F_GUEST_USO4,
|
||||
VIRTIO_NET_F_GUEST_USO6,
|
||||
VIRTIO_NET_F_HOST_USO,
|
||||
|
||||
/* This bit implies RARP isn't sent by QEMU out of band */
|
||||
VIRTIO_NET_F_GUEST_ANNOUNCE,
|
||||
|
||||
VIRTIO_NET_F_MQ,
|
||||
|
||||
VHOST_INVALID_FEATURE_BIT
|
||||
};
|
||||
|
||||
static const int *vhost_net_get_feature_bits(struct vhost_net *net)
|
||||
{
|
||||
const int *feature_bits = 0;
|
||||
|
||||
switch (net->nc->info->type) {
|
||||
case NET_CLIENT_DRIVER_TAP:
|
||||
feature_bits = kernel_feature_bits;
|
||||
break;
|
||||
case NET_CLIENT_DRIVER_VHOST_USER:
|
||||
feature_bits = user_feature_bits;
|
||||
break;
|
||||
#ifdef CONFIG_VHOST_NET_VDPA
|
||||
case NET_CLIENT_DRIVER_VHOST_VDPA:
|
||||
feature_bits = vdpa_feature_bits;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
error_report("Feature bits not defined for this type: %d",
|
||||
net->nc->info->type);
|
||||
break;
|
||||
}
|
||||
|
||||
return feature_bits;
|
||||
}
|
||||
|
||||
uint64_t vhost_net_get_features(struct vhost_net *net, uint64_t features)
|
||||
{
|
||||
return vhost_get_features(&net->dev, vhost_net_get_feature_bits(net),
|
||||
return vhost_get_features(&net->dev, net->feature_bits,
|
||||
features);
|
||||
}
|
||||
int vhost_net_get_config(struct vhost_net *net, uint8_t *config,
|
||||
|
|
@ -140,7 +54,7 @@ int vhost_net_set_config(struct vhost_net *net, const uint8_t *data,
|
|||
void vhost_net_ack_features(struct vhost_net *net, uint64_t features)
|
||||
{
|
||||
net->dev.acked_features = net->dev.backend_features;
|
||||
vhost_ack_features(&net->dev, vhost_net_get_feature_bits(net), features);
|
||||
vhost_ack_features(&net->dev, net->feature_bits, features);
|
||||
}
|
||||
|
||||
uint64_t vhost_net_get_max_queues(VHostNetState *net)
|
||||
|
|
@ -155,11 +69,11 @@ uint64_t vhost_net_get_acked_features(VHostNetState *net)
|
|||
|
||||
void vhost_net_save_acked_features(NetClientState *nc)
|
||||
{
|
||||
#ifdef CONFIG_VHOST_NET_USER
|
||||
if (nc->info->type == NET_CLIENT_DRIVER_VHOST_USER) {
|
||||
vhost_user_save_acked_features(nc);
|
||||
struct vhost_net *net = get_vhost_net(nc);
|
||||
|
||||
if (net && net->save_acked_features) {
|
||||
net->save_acked_features(nc);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void vhost_net_disable_notifiers_nvhosts(VirtIODevice *dev,
|
||||
|
|
@ -329,6 +243,10 @@ struct vhost_net *vhost_net_init(VhostNetOptions *options)
|
|||
}
|
||||
net->nc = options->net_backend;
|
||||
net->dev.nvqs = options->nvqs;
|
||||
net->feature_bits = options->feature_bits;
|
||||
net->save_acked_features = options->save_acked_features;
|
||||
net->max_tx_queue_size = options->max_tx_queue_size;
|
||||
net->is_vhost_user = options->is_vhost_user;
|
||||
|
||||
net->dev.max_queues = 1;
|
||||
net->dev.vqs = net->vqs;
|
||||
|
|
@ -372,9 +290,8 @@ struct vhost_net *vhost_net_init(VhostNetOptions *options)
|
|||
}
|
||||
|
||||
/* Set sane init value. Override when guest acks. */
|
||||
#ifdef CONFIG_VHOST_NET_USER
|
||||
if (net->nc->info->type == NET_CLIENT_DRIVER_VHOST_USER) {
|
||||
features = vhost_user_get_acked_features(net->nc);
|
||||
if (options->get_acked_features) {
|
||||
features = options->get_acked_features(net->nc);
|
||||
if (~net->dev.features & features) {
|
||||
fprintf(stderr, "vhost lacks feature mask 0x%" PRIx64
|
||||
" for backend\n",
|
||||
|
|
@ -382,7 +299,6 @@ struct vhost_net *vhost_net_init(VhostNetOptions *options)
|
|||
goto fail;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
vhost_net_ack_features(net, features);
|
||||
|
||||
|
|
@ -525,7 +441,7 @@ int vhost_net_start(VirtIODevice *dev, NetClientState *ncs,
|
|||
* because vhost user doesn't interrupt masking/unmasking
|
||||
* properly.
|
||||
*/
|
||||
if (net->nc->info->type == NET_CLIENT_DRIVER_VHOST_USER) {
|
||||
if (net->is_vhost_user) {
|
||||
dev->use_guest_notifier_mask = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -551,7 +467,7 @@ int vhost_net_start(VirtIODevice *dev, NetClientState *ncs,
|
|||
|
||||
if (peer->vring_enable) {
|
||||
/* restore vring enable state */
|
||||
r = vhost_set_vring_enable(peer, peer->vring_enable);
|
||||
r = vhost_net_set_vring_enable(peer, peer->vring_enable);
|
||||
|
||||
if (r < 0) {
|
||||
goto err_guest_notifiers;
|
||||
|
|
@ -649,44 +565,21 @@ void vhost_net_config_mask(VHostNetState *net, VirtIODevice *dev, bool mask)
|
|||
{
|
||||
vhost_config_mask(&net->dev, dev, mask);
|
||||
}
|
||||
|
||||
VHostNetState *get_vhost_net(NetClientState *nc)
|
||||
{
|
||||
VHostNetState *vhost_net = 0;
|
||||
|
||||
if (!nc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (nc->info->type) {
|
||||
case NET_CLIENT_DRIVER_TAP:
|
||||
vhost_net = tap_get_vhost_net(nc);
|
||||
/*
|
||||
* tap_get_vhost_net() can return NULL if a tap net-device backend is
|
||||
* created with 'vhost=off' option, 'vhostforce=off' or no vhost or
|
||||
* vhostforce or vhostfd options at all. Please see net_init_tap_one().
|
||||
* Hence, we omit the assertion here.
|
||||
*/
|
||||
break;
|
||||
#ifdef CONFIG_VHOST_NET_USER
|
||||
case NET_CLIENT_DRIVER_VHOST_USER:
|
||||
vhost_net = vhost_user_get_vhost_net(nc);
|
||||
assert(vhost_net);
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_VHOST_NET_VDPA
|
||||
case NET_CLIENT_DRIVER_VHOST_VDPA:
|
||||
vhost_net = vhost_vdpa_get_vhost_net(nc);
|
||||
assert(vhost_net);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
if (nc->info->get_vhost_net) {
|
||||
return nc->info->get_vhost_net(nc);
|
||||
}
|
||||
|
||||
return vhost_net;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int vhost_set_vring_enable(NetClientState *nc, int enable)
|
||||
int vhost_net_set_vring_enable(NetClientState *nc, int enable)
|
||||
{
|
||||
VHostNetState *net = get_vhost_net(nc);
|
||||
const VhostOps *vhost_ops = net->dev.vhost_ops;
|
||||
|
|
|
|||
|
|
@ -670,34 +670,36 @@ static void virtio_net_set_mrg_rx_bufs(VirtIONet *n, int mergeable_rx_bufs,
|
|||
static int virtio_net_max_tx_queue_size(VirtIONet *n)
|
||||
{
|
||||
NetClientState *peer = n->nic_conf.peers.ncs[0];
|
||||
struct vhost_net *net;
|
||||
|
||||
/*
|
||||
* Backends other than vhost-user or vhost-vdpa don't support max queue
|
||||
* size.
|
||||
*/
|
||||
if (!peer) {
|
||||
return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE;
|
||||
goto default_value;
|
||||
}
|
||||
|
||||
switch(peer->info->type) {
|
||||
case NET_CLIENT_DRIVER_VHOST_USER:
|
||||
case NET_CLIENT_DRIVER_VHOST_VDPA:
|
||||
return VIRTQUEUE_MAX_SIZE;
|
||||
default:
|
||||
return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE;
|
||||
};
|
||||
net = get_vhost_net(peer);
|
||||
|
||||
if (!net || !net->max_tx_queue_size) {
|
||||
goto default_value;
|
||||
}
|
||||
|
||||
return net->max_tx_queue_size;
|
||||
|
||||
default_value:
|
||||
return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE;
|
||||
}
|
||||
|
||||
static int peer_attach(VirtIONet *n, int index)
|
||||
{
|
||||
NetClientState *nc = qemu_get_subqueue(n->nic, index);
|
||||
struct vhost_net *net;
|
||||
|
||||
if (!nc->peer) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nc->peer->info->type == NET_CLIENT_DRIVER_VHOST_USER) {
|
||||
vhost_set_vring_enable(nc->peer, 1);
|
||||
net = get_vhost_net(nc->peer);
|
||||
if (net && net->is_vhost_user) {
|
||||
vhost_net_set_vring_enable(nc->peer, 1);
|
||||
}
|
||||
|
||||
if (nc->peer->info->type != NET_CLIENT_DRIVER_TAP) {
|
||||
|
|
@ -714,13 +716,15 @@ static int peer_attach(VirtIONet *n, int index)
|
|||
static int peer_detach(VirtIONet *n, int index)
|
||||
{
|
||||
NetClientState *nc = qemu_get_subqueue(n->nic, index);
|
||||
struct vhost_net *net;
|
||||
|
||||
if (!nc->peer) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nc->peer->info->type == NET_CLIENT_DRIVER_VHOST_USER) {
|
||||
vhost_set_vring_enable(nc->peer, 0);
|
||||
net = get_vhost_net(nc->peer);
|
||||
if (net && net->is_vhost_user) {
|
||||
vhost_net_set_vring_enable(nc->peer, 0);
|
||||
}
|
||||
|
||||
if (nc->peer->info->type != NET_CLIENT_DRIVER_TAP) {
|
||||
|
|
@ -3022,11 +3026,10 @@ static void virtio_net_del_queue(VirtIONet *n, int index)
|
|||
virtio_del_queue(vdev, index * 2 + 1);
|
||||
}
|
||||
|
||||
static void virtio_net_change_num_queue_pairs(VirtIONet *n, int new_max_queue_pairs)
|
||||
static void virtio_net_change_num_queues(VirtIONet *n, int new_num_queues)
|
||||
{
|
||||
VirtIODevice *vdev = VIRTIO_DEVICE(n);
|
||||
int old_num_queues = virtio_get_num_queues(vdev);
|
||||
int new_num_queues = new_max_queue_pairs * 2 + 1;
|
||||
int i;
|
||||
|
||||
assert(old_num_queues >= 3);
|
||||
|
|
@ -3062,16 +3065,14 @@ static void virtio_net_set_multiqueue(VirtIONet *n, int multiqueue)
|
|||
int max = multiqueue ? n->max_queue_pairs : 1;
|
||||
|
||||
n->multiqueue = multiqueue;
|
||||
virtio_net_change_num_queue_pairs(n, max);
|
||||
virtio_net_change_num_queues(n, max * 2 + 1);
|
||||
|
||||
virtio_net_set_queue_pairs(n);
|
||||
}
|
||||
|
||||
static int virtio_net_pre_load_queues(VirtIODevice *vdev)
|
||||
static int virtio_net_pre_load_queues(VirtIODevice *vdev, uint32_t n)
|
||||
{
|
||||
virtio_net_set_multiqueue(VIRTIO_NET(vdev),
|
||||
virtio_has_feature(vdev->guest_features, VIRTIO_NET_F_RSS) ||
|
||||
virtio_has_feature(vdev->guest_features, VIRTIO_NET_F_MQ));
|
||||
virtio_net_change_num_queues(VIRTIO_NET(vdev), n);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3270,13 +3270,6 @@ virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
|
|||
config_len--;
|
||||
}
|
||||
|
||||
if (vdc->pre_load_queues) {
|
||||
ret = vdc->pre_load_queues(vdev);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
num = qemu_get_be32(f);
|
||||
|
||||
if (num > VIRTIO_QUEUE_MAX) {
|
||||
|
|
@ -3284,6 +3277,13 @@ virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (vdc->pre_load_queues) {
|
||||
ret = vdc->pre_load_queues(vdev, num);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
vdev->vq[i].vring.num = qemu_get_be32(f);
|
||||
if (k->has_variable_vring_alignment) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef VHOST_H
|
||||
#define VHOST_H
|
||||
|
||||
#include "net/vhost_net.h"
|
||||
#include "hw/virtio/vhost-backend.h"
|
||||
#include "hw/virtio/virtio.h"
|
||||
#include "system/memory.h"
|
||||
|
|
@ -143,6 +144,10 @@ struct vhost_net {
|
|||
struct vhost_dev dev;
|
||||
struct vhost_virtqueue vqs[2];
|
||||
int backend;
|
||||
const int *feature_bits;
|
||||
int max_tx_queue_size;
|
||||
SaveAcketFeatures *save_acked_features;
|
||||
bool is_vhost_user;
|
||||
NetClientState *nc;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -210,8 +210,14 @@ struct VirtioDeviceClass {
|
|||
void (*guest_notifier_mask)(VirtIODevice *vdev, int n, bool mask);
|
||||
int (*start_ioeventfd)(VirtIODevice *vdev);
|
||||
void (*stop_ioeventfd)(VirtIODevice *vdev);
|
||||
/* Called before loading queues. Useful to add queues before loading. */
|
||||
int (*pre_load_queues)(VirtIODevice *vdev);
|
||||
/*
|
||||
* Called before loading queues.
|
||||
* If the number of queues change at runtime, use @n to know the
|
||||
* number and add or remove queues accordingly.
|
||||
* Note that this function is called in the middle of loading vmsd;
|
||||
* no assumption should be made on states being loaded from vmsd.
|
||||
*/
|
||||
int (*pre_load_queues)(VirtIODevice *vdev, uint32_t n);
|
||||
/* Saving and loading of a device; trying to deprecate save/load
|
||||
* use vmsd for new devices.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ typedef void (SocketReadStateFinalize)(SocketReadState *rs);
|
|||
typedef void (NetAnnounce)(NetClientState *);
|
||||
typedef bool (SetSteeringEBPF)(NetClientState *, int);
|
||||
typedef bool (NetCheckPeerType)(NetClientState *, ObjectClass *, Error **);
|
||||
typedef struct vhost_net *(GetVHostNet)(NetClientState *nc);
|
||||
|
||||
typedef struct NetClientInfo {
|
||||
NetClientDriver type;
|
||||
|
|
@ -92,6 +93,7 @@ typedef struct NetClientInfo {
|
|||
NetAnnounce *announce;
|
||||
SetSteeringEBPF *set_steering_ebpf;
|
||||
NetCheckPeerType *check_peer_type;
|
||||
GetVHostNet *get_vhost_net;
|
||||
} NetClientInfo;
|
||||
|
||||
struct NetClientState {
|
||||
|
|
@ -298,6 +300,7 @@ void net_client_parse(QemuOptsList *opts_list, const char *optstr);
|
|||
void show_netdevs(void);
|
||||
void net_init_clients(void);
|
||||
void net_check_clients(void);
|
||||
void net_client_set_link(NetClientState **ncs, int queues, bool up);
|
||||
void net_cleanup(void);
|
||||
void hmp_host_net_add(Monitor *mon, const QDict *qdict);
|
||||
void hmp_host_net_remove(Monitor *mon, const QDict *qdict);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,4 @@ int tap_disable(NetClientState *nc);
|
|||
|
||||
int tap_get_fd(NetClientState *nc);
|
||||
|
||||
struct vhost_net;
|
||||
struct vhost_net *tap_get_vhost_net(NetClientState *nc);
|
||||
|
||||
#endif /* QEMU_NET_TAP_H */
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* vhost-user.h
|
||||
*
|
||||
* Copyright (c) 2013 Virtual Open Systems Sarl.
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef VHOST_USER_H
|
||||
#define VHOST_USER_H
|
||||
|
||||
struct vhost_net;
|
||||
struct vhost_net *vhost_user_get_vhost_net(NetClientState *nc);
|
||||
uint64_t vhost_user_get_acked_features(NetClientState *nc);
|
||||
void vhost_user_save_acked_features(NetClientState *nc);
|
||||
|
||||
#endif /* VHOST_USER_H */
|
||||
|
|
@ -14,8 +14,4 @@
|
|||
|
||||
#define TYPE_VHOST_VDPA "vhost-vdpa"
|
||||
|
||||
struct vhost_net *vhost_vdpa_get_vhost_net(NetClientState *nc);
|
||||
|
||||
extern const int vdpa_feature_bits[];
|
||||
|
||||
#endif /* VHOST_VDPA_H */
|
||||
|
|
|
|||
|
|
@ -7,11 +7,19 @@
|
|||
struct vhost_net;
|
||||
typedef struct vhost_net VHostNetState;
|
||||
|
||||
typedef uint64_t (GetAckedFeatures)(NetClientState *nc);
|
||||
typedef void (SaveAcketFeatures)(NetClientState *nc);
|
||||
|
||||
typedef struct VhostNetOptions {
|
||||
VhostBackendType backend_type;
|
||||
NetClientState *net_backend;
|
||||
uint32_t busyloop_timeout;
|
||||
unsigned int nvqs;
|
||||
const int *feature_bits;
|
||||
int max_tx_queue_size;
|
||||
bool is_vhost_user;
|
||||
GetAckedFeatures *get_acked_features;
|
||||
SaveAcketFeatures *save_acked_features;
|
||||
void *opaque;
|
||||
} VhostNetOptions;
|
||||
|
||||
|
|
@ -41,7 +49,7 @@ void vhost_net_config_mask(VHostNetState *net, VirtIODevice *dev, bool mask);
|
|||
int vhost_net_notify_migration_done(VHostNetState *net, char* mac_addr);
|
||||
VHostNetState *get_vhost_net(NetClientState *nc);
|
||||
|
||||
int vhost_set_vring_enable(NetClientState * nc, int enable);
|
||||
int vhost_net_set_vring_enable(NetClientState *nc, int enable);
|
||||
|
||||
uint64_t vhost_net_get_acked_features(VHostNetState *net);
|
||||
|
||||
|
|
|
|||
|
|
@ -1285,6 +1285,10 @@ if not get_option('slirp').auto() or have_system
|
|||
endif
|
||||
endif
|
||||
|
||||
enable_passt = get_option('passt') \
|
||||
.require(host_os == 'linux', error_message: 'passt is supported only on Linux') \
|
||||
.allowed()
|
||||
|
||||
vde = not_found
|
||||
if not get_option('vde').auto() or have_system or have_tools
|
||||
vde = cc.find_library('vdeplug', has_headers: ['libvdeplug.h'],
|
||||
|
|
@ -2538,6 +2542,7 @@ if seccomp.found()
|
|||
config_host_data.set('CONFIG_SECCOMP_SYSRAWRC', seccomp_has_sysrawrc)
|
||||
endif
|
||||
config_host_data.set('CONFIG_PIXMAN', pixman.found())
|
||||
config_host_data.set('CONFIG_PASST', enable_passt)
|
||||
config_host_data.set('CONFIG_SLIRP', slirp.found())
|
||||
config_host_data.set('CONFIG_SNAPPY', snappy.found())
|
||||
config_host_data.set('CONFIG_SOLARIS', host_os == 'sunos')
|
||||
|
|
@ -4926,6 +4931,7 @@ if host_os == 'darwin'
|
|||
summary_info += {'vmnet.framework support': vmnet}
|
||||
endif
|
||||
summary_info += {'AF_XDP support': libxdp}
|
||||
summary_info += {'passt support': enable_passt}
|
||||
summary_info += {'slirp support': slirp}
|
||||
summary_info += {'vde support': vde}
|
||||
summary_info += {'netmap support': have_netmap}
|
||||
|
|
|
|||
|
|
@ -234,6 +234,8 @@ option('pixman', type : 'feature', value : 'auto',
|
|||
description: 'pixman support')
|
||||
option('slirp', type: 'feature', value: 'auto',
|
||||
description: 'libslirp user mode network backend support')
|
||||
option('passt', type: 'feature', value: 'auto',
|
||||
description: 'passt network backend support')
|
||||
option('vde', type : 'feature', value : 'auto',
|
||||
description: 'vde network backend support')
|
||||
option('vmnet', type : 'feature', value : 'auto',
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ static int af_xdp_umem_create(AFXDPState *s, int sock_fd, Error **errp)
|
|||
|
||||
s->pool = g_new(uint64_t, n_descs);
|
||||
/* Fill the pool in the opposite order, because it's a LIFO queue. */
|
||||
for (i = n_descs; i >= 0; i--) {
|
||||
for (i = n_descs - 1; i >= 0; i--) {
|
||||
s->pool[i] = i * XSK_UMEM__DEFAULT_FRAME_SIZE;
|
||||
}
|
||||
s->n_pool = n_descs;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@
|
|||
int net_init_dump(const Netdev *netdev, const char *name,
|
||||
NetClientState *peer, Error **errp);
|
||||
|
||||
#ifdef CONFIG_PASST
|
||||
int net_init_passt(const Netdev *netdev, const char *name,
|
||||
NetClientState *peer, Error **errp);
|
||||
#endif
|
||||
#ifdef CONFIG_SLIRP
|
||||
int net_init_slirp(const Netdev *netdev, const char *name,
|
||||
NetClientState *peer, Error **errp);
|
||||
|
|
|
|||
|
|
@ -285,6 +285,9 @@ void net_hub_check_clients(void)
|
|||
case NET_CLIENT_DRIVER_NIC:
|
||||
has_nic = 1;
|
||||
break;
|
||||
#ifdef CONFIG_PASST
|
||||
case NET_CLIENT_DRIVER_PASST:
|
||||
#endif
|
||||
case NET_CLIENT_DRIVER_USER:
|
||||
case NET_CLIENT_DRIVER_TAP:
|
||||
case NET_CLIENT_DRIVER_SOCKET:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
system_ss.add(files(
|
||||
'announce.c',
|
||||
'checksum.c',
|
||||
'dgram.c',
|
||||
'dump.c',
|
||||
'eth.c',
|
||||
'filter-buffer.c',
|
||||
|
|
@ -12,7 +13,7 @@ system_ss.add(files(
|
|||
'queue.c',
|
||||
'socket.c',
|
||||
'stream.c',
|
||||
'dgram.c',
|
||||
'stream_data.c',
|
||||
'util.c',
|
||||
))
|
||||
|
||||
|
|
@ -33,6 +34,9 @@ system_ss.add(when: 'CONFIG_TCG', if_true: files('filter-replay.c'))
|
|||
if have_l2tpv3
|
||||
system_ss.add(files('l2tpv3.c'))
|
||||
endif
|
||||
if enable_passt
|
||||
system_ss.add(files('passt.c'))
|
||||
endif
|
||||
system_ss.add(when: slirp, if_true: files('slirp.c'))
|
||||
system_ss.add(when: vde, if_true: files('vde.c'))
|
||||
if have_netmap
|
||||
|
|
|
|||
36
net/net.c
36
net/net.c
|
|
@ -1248,6 +1248,9 @@ static int (* const net_client_init_fun[NET_CLIENT_DRIVER__MAX])(
|
|||
const char *name,
|
||||
NetClientState *peer, Error **errp) = {
|
||||
[NET_CLIENT_DRIVER_NIC] = net_init_nic,
|
||||
#ifdef CONFIG_PASST
|
||||
[NET_CLIENT_DRIVER_PASST] = net_init_passt,
|
||||
#endif
|
||||
#ifdef CONFIG_SLIRP
|
||||
[NET_CLIENT_DRIVER_USER] = net_init_slirp,
|
||||
#endif
|
||||
|
|
@ -1353,6 +1356,7 @@ void show_netdevs(void)
|
|||
"dgram",
|
||||
"hubport",
|
||||
"tap",
|
||||
"passt",
|
||||
#ifdef CONFIG_SLIRP
|
||||
"user",
|
||||
#endif
|
||||
|
|
@ -1601,21 +1605,11 @@ void colo_notify_filters_event(int event, Error **errp)
|
|||
}
|
||||
}
|
||||
|
||||
void qmp_set_link(const char *name, bool up, Error **errp)
|
||||
void net_client_set_link(NetClientState **ncs, int queues, bool up)
|
||||
{
|
||||
NetClientState *ncs[MAX_QUEUE_NUM];
|
||||
NetClientState *nc;
|
||||
int queues, i;
|
||||
int i;
|
||||
|
||||
queues = qemu_find_net_clients_except(name, ncs,
|
||||
NET_CLIENT_DRIVER__MAX,
|
||||
MAX_QUEUE_NUM);
|
||||
|
||||
if (queues == 0) {
|
||||
error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
|
||||
"Device '%s' not found", name);
|
||||
return;
|
||||
}
|
||||
nc = ncs[0];
|
||||
|
||||
for (i = 0; i < queues; i++) {
|
||||
|
|
@ -1646,6 +1640,24 @@ void qmp_set_link(const char *name, bool up, Error **errp)
|
|||
}
|
||||
}
|
||||
|
||||
void qmp_set_link(const char *name, bool up, Error **errp)
|
||||
{
|
||||
NetClientState *ncs[MAX_QUEUE_NUM];
|
||||
int queues;
|
||||
|
||||
queues = qemu_find_net_clients_except(name, ncs,
|
||||
NET_CLIENT_DRIVER__MAX,
|
||||
MAX_QUEUE_NUM);
|
||||
|
||||
if (queues == 0) {
|
||||
error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
|
||||
"Device '%s' not found", name);
|
||||
return;
|
||||
}
|
||||
|
||||
net_client_set_link(ncs, queues, up);
|
||||
}
|
||||
|
||||
static void net_vm_change_state_handler(void *opaque, bool running,
|
||||
RunState state)
|
||||
{
|
||||
|
|
|
|||
753
net/passt.c
Normal file
753
net/passt.c
Normal file
|
|
@ -0,0 +1,753 @@
|
|||
/*
|
||||
* passt network backend
|
||||
*
|
||||
* Copyright Red Hat
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include <glib/gstdio.h>
|
||||
#include "qemu/error-report.h"
|
||||
#include <gio/gio.h>
|
||||
#include "net/net.h"
|
||||
#include "clients.h"
|
||||
#include "qapi/error.h"
|
||||
#include "io/net-listener.h"
|
||||
#include "chardev/char-fe.h"
|
||||
#include "net/vhost_net.h"
|
||||
#include "hw/virtio/vhost.h"
|
||||
#include "hw/virtio/vhost-user.h"
|
||||
#include "standard-headers/linux/virtio_net.h"
|
||||
#include "stream_data.h"
|
||||
|
||||
#ifdef CONFIG_VHOST_USER
|
||||
static const int user_feature_bits[] = {
|
||||
VIRTIO_F_NOTIFY_ON_EMPTY,
|
||||
VIRTIO_F_NOTIFICATION_DATA,
|
||||
VIRTIO_RING_F_INDIRECT_DESC,
|
||||
VIRTIO_RING_F_EVENT_IDX,
|
||||
|
||||
VIRTIO_F_ANY_LAYOUT,
|
||||
VIRTIO_F_VERSION_1,
|
||||
VIRTIO_NET_F_CSUM,
|
||||
VIRTIO_NET_F_GUEST_CSUM,
|
||||
VIRTIO_NET_F_GSO,
|
||||
VIRTIO_NET_F_GUEST_TSO4,
|
||||
VIRTIO_NET_F_GUEST_TSO6,
|
||||
VIRTIO_NET_F_GUEST_ECN,
|
||||
VIRTIO_NET_F_GUEST_UFO,
|
||||
VIRTIO_NET_F_HOST_TSO4,
|
||||
VIRTIO_NET_F_HOST_TSO6,
|
||||
VIRTIO_NET_F_HOST_ECN,
|
||||
VIRTIO_NET_F_HOST_UFO,
|
||||
VIRTIO_NET_F_MRG_RXBUF,
|
||||
VIRTIO_NET_F_MTU,
|
||||
VIRTIO_F_IOMMU_PLATFORM,
|
||||
VIRTIO_F_RING_PACKED,
|
||||
VIRTIO_F_RING_RESET,
|
||||
VIRTIO_F_IN_ORDER,
|
||||
VIRTIO_NET_F_RSS,
|
||||
VIRTIO_NET_F_RSC_EXT,
|
||||
VIRTIO_NET_F_HASH_REPORT,
|
||||
VIRTIO_NET_F_GUEST_USO4,
|
||||
VIRTIO_NET_F_GUEST_USO6,
|
||||
VIRTIO_NET_F_HOST_USO,
|
||||
|
||||
/* This bit implies RARP isn't sent by QEMU out of band */
|
||||
VIRTIO_NET_F_GUEST_ANNOUNCE,
|
||||
|
||||
VIRTIO_NET_F_MQ,
|
||||
|
||||
VHOST_INVALID_FEATURE_BIT
|
||||
};
|
||||
#endif
|
||||
|
||||
typedef struct NetPasstState {
|
||||
NetStreamData data;
|
||||
GPtrArray *args;
|
||||
gchar *pidfile;
|
||||
pid_t pid;
|
||||
#ifdef CONFIG_VHOST_USER
|
||||
/* vhost user */
|
||||
VhostUserState *vhost_user;
|
||||
VHostNetState *vhost_net;
|
||||
CharBackend vhost_chr;
|
||||
guint vhost_watch;
|
||||
uint64_t acked_features;
|
||||
bool started;
|
||||
#endif
|
||||
} NetPasstState;
|
||||
|
||||
static int net_passt_stream_start(NetPasstState *s, Error **errp);
|
||||
|
||||
static void net_passt_cleanup(NetClientState *nc)
|
||||
{
|
||||
NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
|
||||
|
||||
#ifdef CONFIG_VHOST_USER
|
||||
if (s->vhost_net) {
|
||||
vhost_net_cleanup(s->vhost_net);
|
||||
g_free(s->vhost_net);
|
||||
s->vhost_net = NULL;
|
||||
}
|
||||
if (s->vhost_watch) {
|
||||
g_source_remove(s->vhost_watch);
|
||||
s->vhost_watch = 0;
|
||||
}
|
||||
qemu_chr_fe_deinit(&s->vhost_chr, true);
|
||||
if (s->vhost_user) {
|
||||
vhost_user_cleanup(s->vhost_user);
|
||||
g_free(s->vhost_user);
|
||||
s->vhost_user = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
kill(s->pid, SIGTERM);
|
||||
g_remove(s->pidfile);
|
||||
g_free(s->pidfile);
|
||||
g_ptr_array_free(s->args, TRUE);
|
||||
}
|
||||
|
||||
static ssize_t net_passt_receive(NetClientState *nc, const uint8_t *buf,
|
||||
size_t size)
|
||||
{
|
||||
NetStreamData *d = DO_UPCAST(NetStreamData, nc, nc);
|
||||
|
||||
return net_stream_data_receive(d, buf, size);
|
||||
}
|
||||
|
||||
static gboolean net_passt_send(QIOChannel *ioc, GIOCondition condition,
|
||||
gpointer data)
|
||||
{
|
||||
if (net_stream_data_send(ioc, condition, data) == G_SOURCE_REMOVE) {
|
||||
NetPasstState *s = DO_UPCAST(NetPasstState, data, data);
|
||||
Error *error;
|
||||
|
||||
/* we need to restart passt */
|
||||
kill(s->pid, SIGTERM);
|
||||
if (net_passt_stream_start(s, &error) == -1) {
|
||||
error_report_err(error);
|
||||
}
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VHOST_USER
|
||||
static int passt_set_vnet_endianness(NetClientState *nc, bool enable)
|
||||
{
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool passt_has_vnet_hdr(NetClientState *nc)
|
||||
{
|
||||
NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
|
||||
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
|
||||
|
||||
return s->vhost_user != NULL;
|
||||
}
|
||||
|
||||
static bool passt_has_ufo(NetClientState *nc)
|
||||
{
|
||||
NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
|
||||
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
|
||||
|
||||
return s->vhost_user != NULL;
|
||||
}
|
||||
|
||||
static bool passt_check_peer_type(NetClientState *nc, ObjectClass *oc,
|
||||
Error **errp)
|
||||
{
|
||||
NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
|
||||
const char *driver = object_class_get_name(oc);
|
||||
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
|
||||
|
||||
if (s->vhost_user == NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!g_str_has_prefix(driver, "virtio-net-")) {
|
||||
error_setg(errp, "vhost-user requires frontend driver virtio-net-*");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct vhost_net *passt_get_vhost_net(NetClientState *nc)
|
||||
{
|
||||
NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
|
||||
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
|
||||
|
||||
return s->vhost_net;
|
||||
}
|
||||
|
||||
static uint64_t passt_get_acked_features(NetClientState *nc)
|
||||
{
|
||||
NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
|
||||
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
|
||||
|
||||
return s->acked_features;
|
||||
}
|
||||
|
||||
static void passt_save_acked_features(NetClientState *nc)
|
||||
{
|
||||
NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
|
||||
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
|
||||
|
||||
if (s->vhost_net) {
|
||||
uint64_t features = vhost_net_get_acked_features(s->vhost_net);
|
||||
if (features) {
|
||||
s->acked_features = features;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static NetClientInfo net_passt_info = {
|
||||
.type = NET_CLIENT_DRIVER_PASST,
|
||||
.size = sizeof(NetPasstState),
|
||||
.receive = net_passt_receive,
|
||||
.cleanup = net_passt_cleanup,
|
||||
#ifdef CONFIG_VHOST_USER
|
||||
.has_vnet_hdr = passt_has_vnet_hdr,
|
||||
.has_ufo = passt_has_ufo,
|
||||
.set_vnet_be = passt_set_vnet_endianness,
|
||||
.set_vnet_le = passt_set_vnet_endianness,
|
||||
.check_peer_type = passt_check_peer_type,
|
||||
.get_vhost_net = passt_get_vhost_net,
|
||||
#endif
|
||||
};
|
||||
|
||||
static void net_passt_client_connected(QIOTask *task, gpointer opaque)
|
||||
{
|
||||
NetPasstState *s = opaque;
|
||||
|
||||
if (net_stream_data_client_connected(task, &s->data) == 0) {
|
||||
qemu_set_info_str(&s->data.nc, "stream,connected to pid %d", s->pid);
|
||||
}
|
||||
}
|
||||
|
||||
static int net_passt_start_daemon(NetPasstState *s, int sock, Error **errp)
|
||||
{
|
||||
g_autoptr(GSubprocess) daemon = NULL;
|
||||
g_autofree gchar *contents = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
GSubprocessLauncher *launcher;
|
||||
|
||||
qemu_set_info_str(&s->data.nc, "launching passt");
|
||||
|
||||
launcher = g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_NONE);
|
||||
g_subprocess_launcher_take_fd(launcher, sock, 3);
|
||||
|
||||
daemon = g_subprocess_launcher_spawnv(launcher,
|
||||
(const gchar *const *)s->args->pdata,
|
||||
&error);
|
||||
g_object_unref(launcher);
|
||||
|
||||
if (!daemon) {
|
||||
error_setg(errp, "Error creating daemon: %s", error->message);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!g_subprocess_wait(daemon, NULL, &error)) {
|
||||
error_setg(errp, "Error waiting for daemon: %s", error->message);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (g_subprocess_get_if_exited(daemon) &&
|
||||
g_subprocess_get_exit_status(daemon)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!g_file_get_contents(s->pidfile, &contents, NULL, &error)) {
|
||||
error_setg(errp, "Cannot read passt pid: %s", error->message);
|
||||
return -1;
|
||||
}
|
||||
|
||||
s->pid = (pid_t)g_ascii_strtoll(contents, NULL, 10);
|
||||
if (s->pid <= 0) {
|
||||
error_setg(errp, "File '%s' did not contain a valid PID.", s->pidfile);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int net_passt_stream_start(NetPasstState *s, Error **errp)
|
||||
{
|
||||
QIOChannelSocket *sioc;
|
||||
SocketAddress *addr;
|
||||
int sv[2];
|
||||
|
||||
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
|
||||
error_setg_errno(errp, errno, "socketpair() failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* connect to passt */
|
||||
qemu_set_info_str(&s->data.nc, "connecting to passt");
|
||||
|
||||
/* create socket channel */
|
||||
sioc = qio_channel_socket_new();
|
||||
s->data.ioc = QIO_CHANNEL(sioc);
|
||||
s->data.nc.link_down = true;
|
||||
s->data.send = net_passt_send;
|
||||
|
||||
addr = g_new0(SocketAddress, 1);
|
||||
addr->type = SOCKET_ADDRESS_TYPE_FD;
|
||||
addr->u.fd.str = g_strdup_printf("%d", sv[0]);
|
||||
|
||||
qio_channel_socket_connect_async(sioc, addr,
|
||||
net_passt_client_connected, s,
|
||||
NULL, NULL);
|
||||
|
||||
qapi_free_SocketAddress(addr);
|
||||
|
||||
/* start passt */
|
||||
if (net_passt_start_daemon(s, sv[1], errp) == -1) {
|
||||
close(sv[0]);
|
||||
close(sv[1]);
|
||||
return -1;
|
||||
}
|
||||
close(sv[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VHOST_USER
|
||||
static gboolean passt_vhost_user_watch(void *do_not_use, GIOCondition cond,
|
||||
void *opaque)
|
||||
{
|
||||
NetPasstState *s = opaque;
|
||||
|
||||
qemu_chr_fe_disconnect(&s->vhost_chr);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void passt_vhost_user_event(void *opaque, QEMUChrEvent event);
|
||||
|
||||
static void chr_closed_bh(void *opaque)
|
||||
{
|
||||
NetPasstState *s = opaque;
|
||||
|
||||
passt_save_acked_features(&s->data.nc);
|
||||
|
||||
net_client_set_link(&(NetClientState *){ &s->data.nc }, 1, false);
|
||||
|
||||
qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, passt_vhost_user_event,
|
||||
NULL, s, NULL, true);
|
||||
}
|
||||
|
||||
static void passt_vhost_user_stop(NetPasstState *s)
|
||||
{
|
||||
passt_save_acked_features(&s->data.nc);
|
||||
vhost_net_cleanup(s->vhost_net);
|
||||
}
|
||||
|
||||
static int passt_vhost_user_start(NetPasstState *s, VhostUserState *be)
|
||||
{
|
||||
struct vhost_net *net = NULL;
|
||||
VhostNetOptions options;
|
||||
|
||||
options.backend_type = VHOST_BACKEND_TYPE_USER;
|
||||
options.net_backend = &s->data.nc;
|
||||
options.opaque = be;
|
||||
options.busyloop_timeout = 0;
|
||||
options.nvqs = 2;
|
||||
options.feature_bits = user_feature_bits;
|
||||
options.max_tx_queue_size = VIRTQUEUE_MAX_SIZE;
|
||||
options.get_acked_features = passt_get_acked_features;
|
||||
options.save_acked_features = passt_save_acked_features;
|
||||
options.is_vhost_user = true;
|
||||
|
||||
net = vhost_net_init(&options);
|
||||
if (!net) {
|
||||
error_report("failed to init passt vhost_net");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (s->vhost_net) {
|
||||
vhost_net_cleanup(s->vhost_net);
|
||||
g_free(s->vhost_net);
|
||||
}
|
||||
s->vhost_net = net;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
if (net) {
|
||||
vhost_net_cleanup(net);
|
||||
g_free(net);
|
||||
}
|
||||
passt_vhost_user_stop(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void passt_vhost_user_event(void *opaque, QEMUChrEvent event)
|
||||
{
|
||||
NetPasstState *s = opaque;
|
||||
Error *err = NULL;
|
||||
|
||||
switch (event) {
|
||||
case CHR_EVENT_OPENED:
|
||||
if (passt_vhost_user_start(s, s->vhost_user) < 0) {
|
||||
qemu_chr_fe_disconnect(&s->vhost_chr);
|
||||
return;
|
||||
}
|
||||
s->vhost_watch = qemu_chr_fe_add_watch(&s->vhost_chr, G_IO_HUP,
|
||||
passt_vhost_user_watch, s);
|
||||
net_client_set_link(&(NetClientState *){ &s->data.nc }, 1, true);
|
||||
s->started = true;
|
||||
break;
|
||||
case CHR_EVENT_CLOSED:
|
||||
if (s->vhost_watch) {
|
||||
AioContext *ctx = qemu_get_current_aio_context();
|
||||
|
||||
g_source_remove(s->vhost_watch);
|
||||
s->vhost_watch = 0;
|
||||
qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, false);
|
||||
|
||||
aio_bh_schedule_oneshot(ctx, chr_closed_bh, s);
|
||||
}
|
||||
break;
|
||||
case CHR_EVENT_BREAK:
|
||||
case CHR_EVENT_MUX_IN:
|
||||
case CHR_EVENT_MUX_OUT:
|
||||
/* Ignore */
|
||||
break;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
error_report_err(err);
|
||||
}
|
||||
}
|
||||
|
||||
static int net_passt_vhost_user_init(NetPasstState *s, Error **errp)
|
||||
{
|
||||
Chardev *chr;
|
||||
int sv[2];
|
||||
|
||||
if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
|
||||
error_setg_errno(errp, errno, "socketpair() failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* connect to passt */
|
||||
qemu_set_info_str(&s->data.nc, "connecting to passt");
|
||||
|
||||
/* create chardev */
|
||||
|
||||
chr = CHARDEV(object_new(TYPE_CHARDEV_SOCKET));
|
||||
if (!chr || qemu_chr_add_client(chr, sv[0]) == -1) {
|
||||
object_unref(OBJECT(chr));
|
||||
error_setg(errp, "Failed to make socket chardev");
|
||||
goto err;
|
||||
}
|
||||
|
||||
s->vhost_user = g_new0(struct VhostUserState, 1);
|
||||
if (!qemu_chr_fe_init(&s->vhost_chr, chr, errp) ||
|
||||
!vhost_user_init(s->vhost_user, &s->vhost_chr, errp)) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* start passt */
|
||||
if (net_passt_start_daemon(s, sv[1], errp) == -1) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
do {
|
||||
if (qemu_chr_fe_wait_connected(&s->vhost_chr, errp) < 0) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL,
|
||||
passt_vhost_user_event, NULL, s, NULL,
|
||||
true);
|
||||
} while (!s->started);
|
||||
|
||||
qemu_set_info_str(&s->data.nc, "vhost-user,connected to pid %d", s->pid);
|
||||
|
||||
close(sv[1]);
|
||||
return 0;
|
||||
err:
|
||||
close(sv[0]);
|
||||
close(sv[1]);
|
||||
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
static int net_passt_vhost_user_init(NetPasstState *s, Error **errp)
|
||||
{
|
||||
error_setg(errp, "vhost-user support has not been built");
|
||||
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static GPtrArray *net_passt_decode_args(const NetDevPasstOptions *passt,
|
||||
gchar *pidfile, Error **errp)
|
||||
{
|
||||
GPtrArray *args = g_ptr_array_new_with_free_func(g_free);
|
||||
|
||||
if (passt->path) {
|
||||
g_ptr_array_add(args, g_strdup(passt->path));
|
||||
} else {
|
||||
g_ptr_array_add(args, g_strdup("passt"));
|
||||
}
|
||||
|
||||
if (passt->has_vhost_user && passt->vhost_user) {
|
||||
g_ptr_array_add(args, g_strdup("--vhost-user"));
|
||||
}
|
||||
|
||||
/* by default, be quiet */
|
||||
if (!passt->has_quiet || passt->quiet) {
|
||||
g_ptr_array_add(args, g_strdup("--quiet"));
|
||||
}
|
||||
|
||||
if (passt->has_mtu) {
|
||||
g_ptr_array_add(args, g_strdup("--mtu"));
|
||||
g_ptr_array_add(args, g_strdup_printf("%"PRId64, passt->mtu));
|
||||
}
|
||||
|
||||
if (passt->address) {
|
||||
g_ptr_array_add(args, g_strdup("--address"));
|
||||
g_ptr_array_add(args, g_strdup(passt->address));
|
||||
}
|
||||
|
||||
if (passt->netmask) {
|
||||
g_ptr_array_add(args, g_strdup("--netmask"));
|
||||
g_ptr_array_add(args, g_strdup(passt->netmask));
|
||||
}
|
||||
|
||||
if (passt->mac) {
|
||||
g_ptr_array_add(args, g_strdup("--mac-addr"));
|
||||
g_ptr_array_add(args, g_strdup(passt->mac));
|
||||
}
|
||||
|
||||
if (passt->gateway) {
|
||||
g_ptr_array_add(args, g_strdup("--gateway"));
|
||||
g_ptr_array_add(args, g_strdup(passt->gateway));
|
||||
}
|
||||
|
||||
if (passt->interface) {
|
||||
g_ptr_array_add(args, g_strdup("--interface"));
|
||||
g_ptr_array_add(args, g_strdup(passt->interface));
|
||||
}
|
||||
|
||||
if (passt->outbound) {
|
||||
g_ptr_array_add(args, g_strdup("--outbound"));
|
||||
g_ptr_array_add(args, g_strdup(passt->outbound));
|
||||
}
|
||||
|
||||
if (passt->outbound_if4) {
|
||||
g_ptr_array_add(args, g_strdup("--outbound-if4"));
|
||||
g_ptr_array_add(args, g_strdup(passt->outbound_if4));
|
||||
}
|
||||
|
||||
if (passt->outbound_if6) {
|
||||
g_ptr_array_add(args, g_strdup("--outbound-if6"));
|
||||
g_ptr_array_add(args, g_strdup(passt->outbound_if6));
|
||||
}
|
||||
|
||||
if (passt->dns) {
|
||||
g_ptr_array_add(args, g_strdup("--dns"));
|
||||
g_ptr_array_add(args, g_strdup(passt->dns));
|
||||
}
|
||||
if (passt->fqdn) {
|
||||
g_ptr_array_add(args, g_strdup("--fqdn"));
|
||||
g_ptr_array_add(args, g_strdup(passt->fqdn));
|
||||
}
|
||||
|
||||
if (passt->has_dhcp_dns && !passt->dhcp_dns) {
|
||||
g_ptr_array_add(args, g_strdup("--no-dhcp-dns"));
|
||||
}
|
||||
|
||||
if (passt->has_dhcp_search && !passt->dhcp_search) {
|
||||
g_ptr_array_add(args, g_strdup("--no-dhcp-search"));
|
||||
}
|
||||
|
||||
if (passt->map_host_loopback) {
|
||||
g_ptr_array_add(args, g_strdup("--map-host-loopback"));
|
||||
g_ptr_array_add(args, g_strdup(passt->map_host_loopback));
|
||||
}
|
||||
|
||||
if (passt->map_guest_addr) {
|
||||
g_ptr_array_add(args, g_strdup("--map-guest-addr"));
|
||||
g_ptr_array_add(args, g_strdup(passt->map_guest_addr));
|
||||
}
|
||||
|
||||
if (passt->dns_forward) {
|
||||
g_ptr_array_add(args, g_strdup("--dns-forward"));
|
||||
g_ptr_array_add(args, g_strdup(passt->dns_forward));
|
||||
}
|
||||
|
||||
if (passt->dns_host) {
|
||||
g_ptr_array_add(args, g_strdup("--dns-host"));
|
||||
g_ptr_array_add(args, g_strdup(passt->dns_host));
|
||||
}
|
||||
|
||||
if (passt->has_tcp && !passt->tcp) {
|
||||
g_ptr_array_add(args, g_strdup("--no-tcp"));
|
||||
}
|
||||
|
||||
if (passt->has_udp && !passt->udp) {
|
||||
g_ptr_array_add(args, g_strdup("--no-udp"));
|
||||
}
|
||||
|
||||
if (passt->has_icmp && !passt->icmp) {
|
||||
g_ptr_array_add(args, g_strdup("--no-icmp"));
|
||||
}
|
||||
|
||||
if (passt->has_dhcp && !passt->dhcp) {
|
||||
g_ptr_array_add(args, g_strdup("--no-dhcp"));
|
||||
}
|
||||
|
||||
if (passt->has_ndp && !passt->ndp) {
|
||||
g_ptr_array_add(args, g_strdup("--no-ndp"));
|
||||
}
|
||||
if (passt->has_dhcpv6 && !passt->dhcpv6) {
|
||||
g_ptr_array_add(args, g_strdup("--no-dhcpv6"));
|
||||
}
|
||||
|
||||
if (passt->has_ra && !passt->ra) {
|
||||
g_ptr_array_add(args, g_strdup("--no-ra"));
|
||||
}
|
||||
|
||||
if (passt->has_freebind && passt->freebind) {
|
||||
g_ptr_array_add(args, g_strdup("--freebind"));
|
||||
}
|
||||
|
||||
if (passt->has_ipv4 && !passt->ipv4) {
|
||||
g_ptr_array_add(args, g_strdup("--ipv6-only"));
|
||||
}
|
||||
|
||||
if (passt->has_ipv6 && !passt->ipv6) {
|
||||
g_ptr_array_add(args, g_strdup("--ipv4-only"));
|
||||
}
|
||||
|
||||
if (passt->has_search && passt->search) {
|
||||
const StringList *list = passt->search;
|
||||
GString *domains = g_string_new(list->value->str);
|
||||
|
||||
list = list->next;
|
||||
while (list) {
|
||||
g_string_append(domains, " ");
|
||||
g_string_append(domains, list->value->str);
|
||||
list = list->next;
|
||||
}
|
||||
|
||||
g_ptr_array_add(args, g_strdup("--search"));
|
||||
g_ptr_array_add(args, g_string_free(domains, FALSE));
|
||||
}
|
||||
|
||||
if (passt->has_tcp_ports && passt->tcp_ports) {
|
||||
const StringList *list = passt->tcp_ports;
|
||||
GString *tcp_ports = g_string_new(list->value->str);
|
||||
|
||||
list = list->next;
|
||||
while (list) {
|
||||
g_string_append(tcp_ports, ",");
|
||||
g_string_append(tcp_ports, list->value->str);
|
||||
list = list->next;
|
||||
}
|
||||
|
||||
g_ptr_array_add(args, g_strdup("--tcp-ports"));
|
||||
g_ptr_array_add(args, g_string_free(tcp_ports, FALSE));
|
||||
}
|
||||
|
||||
if (passt->has_udp_ports && passt->udp_ports) {
|
||||
const StringList *list = passt->udp_ports;
|
||||
GString *udp_ports = g_string_new(list->value->str);
|
||||
|
||||
list = list->next;
|
||||
while (list) {
|
||||
g_string_append(udp_ports, ",");
|
||||
g_string_append(udp_ports, list->value->str);
|
||||
list = list->next;
|
||||
}
|
||||
|
||||
g_ptr_array_add(args, g_strdup("--udp-ports"));
|
||||
g_ptr_array_add(args, g_string_free(udp_ports, FALSE));
|
||||
}
|
||||
|
||||
if (passt->has_param && passt->param) {
|
||||
const StringList *list = passt->param;
|
||||
|
||||
while (list) {
|
||||
g_ptr_array_add(args, g_strdup(list->value->str));
|
||||
list = list->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* provide a pid file to be able to kil passt on exit */
|
||||
g_ptr_array_add(args, g_strdup("--pid"));
|
||||
g_ptr_array_add(args, g_strdup(pidfile));
|
||||
|
||||
/* g_subprocess_launcher_take_fd() will set the socket on fd 3 */
|
||||
g_ptr_array_add(args, g_strdup("--fd"));
|
||||
g_ptr_array_add(args, g_strdup("3"));
|
||||
|
||||
g_ptr_array_add(args, NULL);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
int net_init_passt(const Netdev *netdev, const char *name,
|
||||
NetClientState *peer, Error **errp)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
NetClientState *nc;
|
||||
NetPasstState *s;
|
||||
GPtrArray *args;
|
||||
gchar *pidfile;
|
||||
int pidfd;
|
||||
|
||||
assert(netdev->type == NET_CLIENT_DRIVER_PASST);
|
||||
|
||||
pidfd = g_file_open_tmp("passt-XXXXXX.pid", &pidfile, &error);
|
||||
if (pidfd == -1) {
|
||||
error_setg(errp, "Failed to create temporary file: %s", error->message);
|
||||
return -1;
|
||||
}
|
||||
close(pidfd);
|
||||
|
||||
args = net_passt_decode_args(&netdev->u.passt, pidfile, errp);
|
||||
if (args == NULL) {
|
||||
g_free(pidfile);
|
||||
return -1;
|
||||
}
|
||||
|
||||
nc = qemu_new_net_client(&net_passt_info, peer, "passt", name);
|
||||
s = DO_UPCAST(NetPasstState, data.nc, nc);
|
||||
|
||||
s->args = args;
|
||||
s->pidfile = pidfile;
|
||||
|
||||
if (netdev->u.passt.has_vhost_user && netdev->u.passt.vhost_user) {
|
||||
if (net_passt_vhost_user_init(s, errp) == -1) {
|
||||
qemu_del_net_client(nc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (net_passt_stream_start(s, errp) == -1) {
|
||||
qemu_del_net_client(nc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
282
net/stream.c
282
net/stream.c
|
|
@ -27,173 +27,50 @@
|
|||
|
||||
#include "net/net.h"
|
||||
#include "clients.h"
|
||||
#include "monitor/monitor.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/option.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "qemu/iov.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "io/channel.h"
|
||||
#include "io/channel-socket.h"
|
||||
#include "io/net-listener.h"
|
||||
#include "qapi/qapi-events-net.h"
|
||||
#include "qapi/qapi-visit-sockets.h"
|
||||
#include "qapi/clone-visitor.h"
|
||||
|
||||
#include "stream_data.h"
|
||||
|
||||
typedef struct NetStreamState {
|
||||
NetClientState nc;
|
||||
QIOChannel *listen_ioc;
|
||||
QIONetListener *listener;
|
||||
QIOChannel *ioc;
|
||||
guint ioc_read_tag;
|
||||
guint ioc_write_tag;
|
||||
SocketReadState rs;
|
||||
unsigned int send_index; /* number of bytes sent*/
|
||||
NetStreamData data;
|
||||
uint32_t reconnect_ms;
|
||||
guint timer_tag;
|
||||
SocketAddress *addr;
|
||||
} NetStreamState;
|
||||
|
||||
static void net_stream_listen(QIONetListener *listener,
|
||||
QIOChannelSocket *cioc,
|
||||
void *opaque);
|
||||
static void net_stream_arm_reconnect(NetStreamState *s);
|
||||
|
||||
static gboolean net_stream_writable(QIOChannel *ioc,
|
||||
GIOCondition condition,
|
||||
gpointer data)
|
||||
{
|
||||
NetStreamState *s = data;
|
||||
|
||||
s->ioc_write_tag = 0;
|
||||
|
||||
qemu_flush_queued_packets(&s->nc);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static ssize_t net_stream_receive(NetClientState *nc, const uint8_t *buf,
|
||||
size_t size)
|
||||
{
|
||||
NetStreamState *s = DO_UPCAST(NetStreamState, nc, nc);
|
||||
uint32_t len = htonl(size);
|
||||
struct iovec iov[] = {
|
||||
{
|
||||
.iov_base = &len,
|
||||
.iov_len = sizeof(len),
|
||||
}, {
|
||||
.iov_base = (void *)buf,
|
||||
.iov_len = size,
|
||||
},
|
||||
};
|
||||
struct iovec local_iov[2];
|
||||
unsigned int nlocal_iov;
|
||||
size_t remaining;
|
||||
ssize_t ret;
|
||||
NetStreamData *d = DO_UPCAST(NetStreamData, nc, nc);
|
||||
|
||||
remaining = iov_size(iov, 2) - s->send_index;
|
||||
nlocal_iov = iov_copy(local_iov, 2, iov, 2, s->send_index, remaining);
|
||||
ret = qio_channel_writev(s->ioc, local_iov, nlocal_iov, NULL);
|
||||
if (ret == QIO_CHANNEL_ERR_BLOCK) {
|
||||
ret = 0; /* handled further down */
|
||||
}
|
||||
if (ret == -1) {
|
||||
s->send_index = 0;
|
||||
return -errno;
|
||||
}
|
||||
if (ret < (ssize_t)remaining) {
|
||||
s->send_index += ret;
|
||||
s->ioc_write_tag = qio_channel_add_watch(s->ioc, G_IO_OUT,
|
||||
net_stream_writable, s, NULL);
|
||||
return 0;
|
||||
}
|
||||
s->send_index = 0;
|
||||
return size;
|
||||
}
|
||||
|
||||
static gboolean net_stream_send(QIOChannel *ioc,
|
||||
GIOCondition condition,
|
||||
gpointer data);
|
||||
|
||||
static void net_stream_send_completed(NetClientState *nc, ssize_t len)
|
||||
{
|
||||
NetStreamState *s = DO_UPCAST(NetStreamState, nc, nc);
|
||||
|
||||
if (!s->ioc_read_tag) {
|
||||
s->ioc_read_tag = qio_channel_add_watch(s->ioc, G_IO_IN,
|
||||
net_stream_send, s, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void net_stream_rs_finalize(SocketReadState *rs)
|
||||
{
|
||||
NetStreamState *s = container_of(rs, NetStreamState, rs);
|
||||
|
||||
if (qemu_send_packet_async(&s->nc, rs->buf,
|
||||
rs->packet_len,
|
||||
net_stream_send_completed) == 0) {
|
||||
if (s->ioc_read_tag) {
|
||||
g_source_remove(s->ioc_read_tag);
|
||||
s->ioc_read_tag = 0;
|
||||
}
|
||||
}
|
||||
return net_stream_data_receive(d, buf, size);
|
||||
}
|
||||
|
||||
static gboolean net_stream_send(QIOChannel *ioc,
|
||||
GIOCondition condition,
|
||||
gpointer data)
|
||||
{
|
||||
NetStreamState *s = data;
|
||||
int size;
|
||||
int ret;
|
||||
QEMU_UNINITIALIZED char buf1[NET_BUFSIZE];
|
||||
const char *buf;
|
||||
if (net_stream_data_send(ioc, condition, data) == G_SOURCE_REMOVE) {
|
||||
NetStreamState *s = DO_UPCAST(NetStreamState, data, data);
|
||||
|
||||
size = qio_channel_read(s->ioc, buf1, sizeof(buf1), NULL);
|
||||
if (size < 0) {
|
||||
if (errno != EWOULDBLOCK) {
|
||||
goto eoc;
|
||||
}
|
||||
} else if (size == 0) {
|
||||
/* end of connection */
|
||||
eoc:
|
||||
s->ioc_read_tag = 0;
|
||||
if (s->ioc_write_tag) {
|
||||
g_source_remove(s->ioc_write_tag);
|
||||
s->ioc_write_tag = 0;
|
||||
}
|
||||
if (s->listener) {
|
||||
qemu_set_info_str(&s->nc, "listening");
|
||||
qio_net_listener_set_client_func(s->listener, net_stream_listen,
|
||||
s, NULL);
|
||||
}
|
||||
object_unref(OBJECT(s->ioc));
|
||||
s->ioc = NULL;
|
||||
|
||||
net_socket_rs_init(&s->rs, net_stream_rs_finalize, false);
|
||||
s->nc.link_down = true;
|
||||
|
||||
qapi_event_send_netdev_stream_disconnected(s->nc.name);
|
||||
qapi_event_send_netdev_stream_disconnected(s->data.nc.name);
|
||||
net_stream_arm_reconnect(s);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
buf = buf1;
|
||||
|
||||
ret = net_fill_rstate(&s->rs, (const uint8_t *)buf, size);
|
||||
|
||||
if (ret == -1) {
|
||||
goto eoc;
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void net_stream_cleanup(NetClientState *nc)
|
||||
{
|
||||
NetStreamState *s = DO_UPCAST(NetStreamState, nc, nc);
|
||||
NetStreamState *s = DO_UPCAST(NetStreamState, data.nc, nc);
|
||||
if (s->timer_tag) {
|
||||
g_source_remove(s->timer_tag);
|
||||
s->timer_tag = 0;
|
||||
|
|
@ -202,28 +79,28 @@ static void net_stream_cleanup(NetClientState *nc)
|
|||
qapi_free_SocketAddress(s->addr);
|
||||
s->addr = NULL;
|
||||
}
|
||||
if (s->ioc) {
|
||||
if (QIO_CHANNEL_SOCKET(s->ioc)->fd != -1) {
|
||||
if (s->ioc_read_tag) {
|
||||
g_source_remove(s->ioc_read_tag);
|
||||
s->ioc_read_tag = 0;
|
||||
if (s->data.ioc) {
|
||||
if (QIO_CHANNEL_SOCKET(s->data.ioc)->fd != -1) {
|
||||
if (s->data.ioc_read_tag) {
|
||||
g_source_remove(s->data.ioc_read_tag);
|
||||
s->data.ioc_read_tag = 0;
|
||||
}
|
||||
if (s->ioc_write_tag) {
|
||||
g_source_remove(s->ioc_write_tag);
|
||||
s->ioc_write_tag = 0;
|
||||
if (s->data.ioc_write_tag) {
|
||||
g_source_remove(s->data.ioc_write_tag);
|
||||
s->data.ioc_write_tag = 0;
|
||||
}
|
||||
}
|
||||
object_unref(OBJECT(s->ioc));
|
||||
s->ioc = NULL;
|
||||
object_unref(OBJECT(s->data.ioc));
|
||||
s->data.ioc = NULL;
|
||||
}
|
||||
if (s->listen_ioc) {
|
||||
if (s->listener) {
|
||||
qio_net_listener_disconnect(s->listener);
|
||||
object_unref(OBJECT(s->listener));
|
||||
s->listener = NULL;
|
||||
if (s->data.listen_ioc) {
|
||||
if (s->data.listener) {
|
||||
qio_net_listener_disconnect(s->data.listener);
|
||||
object_unref(OBJECT(s->data.listener));
|
||||
s->data.listener = NULL;
|
||||
}
|
||||
object_unref(OBJECT(s->listen_ioc));
|
||||
s->listen_ioc = NULL;
|
||||
object_unref(OBJECT(s->data.listen_ioc));
|
||||
s->data.listen_ioc = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,23 +112,13 @@ static NetClientInfo net_stream_info = {
|
|||
};
|
||||
|
||||
static void net_stream_listen(QIONetListener *listener,
|
||||
QIOChannelSocket *cioc,
|
||||
void *opaque)
|
||||
QIOChannelSocket *cioc, gpointer data)
|
||||
{
|
||||
NetStreamState *s = opaque;
|
||||
NetStreamData *d = data;
|
||||
SocketAddress *addr;
|
||||
char *uri;
|
||||
|
||||
object_ref(OBJECT(cioc));
|
||||
|
||||
qio_net_listener_set_client_func(s->listener, NULL, s, NULL);
|
||||
|
||||
s->ioc = QIO_CHANNEL(cioc);
|
||||
qio_channel_set_name(s->ioc, "stream-server");
|
||||
s->nc.link_down = false;
|
||||
|
||||
s->ioc_read_tag = qio_channel_add_watch(s->ioc, G_IO_IN, net_stream_send,
|
||||
s, NULL);
|
||||
net_stream_data_listen(listener, cioc, data);
|
||||
|
||||
if (cioc->localAddr.ss_family == AF_UNIX) {
|
||||
addr = qio_channel_socket_get_local_address(cioc, NULL);
|
||||
|
|
@ -260,22 +127,22 @@ static void net_stream_listen(QIONetListener *listener,
|
|||
}
|
||||
g_assert(addr != NULL);
|
||||
uri = socket_uri(addr);
|
||||
qemu_set_info_str(&s->nc, "%s", uri);
|
||||
qemu_set_info_str(&d->nc, "%s", uri);
|
||||
g_free(uri);
|
||||
qapi_event_send_netdev_stream_connected(s->nc.name, addr);
|
||||
qapi_event_send_netdev_stream_connected(d->nc.name, addr);
|
||||
qapi_free_SocketAddress(addr);
|
||||
}
|
||||
|
||||
static void net_stream_server_listening(QIOTask *task, gpointer opaque)
|
||||
{
|
||||
NetStreamState *s = opaque;
|
||||
QIOChannelSocket *listen_sioc = QIO_CHANNEL_SOCKET(s->listen_ioc);
|
||||
NetStreamData *d = opaque;
|
||||
QIOChannelSocket *listen_sioc = QIO_CHANNEL_SOCKET(d->listen_ioc);
|
||||
SocketAddress *addr;
|
||||
int ret;
|
||||
Error *err = NULL;
|
||||
|
||||
if (qio_task_propagate_error(task, &err)) {
|
||||
qemu_set_info_str(&s->nc, "error: %s", error_get_pretty(err));
|
||||
qemu_set_info_str(&d->nc, "error: %s", error_get_pretty(err));
|
||||
error_free(err);
|
||||
return;
|
||||
}
|
||||
|
|
@ -284,20 +151,21 @@ static void net_stream_server_listening(QIOTask *task, gpointer opaque)
|
|||
g_assert(addr != NULL);
|
||||
ret = qemu_socket_try_set_nonblock(listen_sioc->fd);
|
||||
if (addr->type == SOCKET_ADDRESS_TYPE_FD && ret < 0) {
|
||||
qemu_set_info_str(&s->nc, "can't use file descriptor %s (errno %d)",
|
||||
qemu_set_info_str(&d->nc, "can't use file descriptor %s (errno %d)",
|
||||
addr->u.fd.str, -ret);
|
||||
return;
|
||||
}
|
||||
g_assert(ret == 0);
|
||||
qapi_free_SocketAddress(addr);
|
||||
|
||||
s->nc.link_down = true;
|
||||
s->listener = qio_net_listener_new();
|
||||
d->nc.link_down = true;
|
||||
d->listener = qio_net_listener_new();
|
||||
|
||||
qemu_set_info_str(&s->nc, "listening");
|
||||
net_socket_rs_init(&s->rs, net_stream_rs_finalize, false);
|
||||
qio_net_listener_set_client_func(s->listener, net_stream_listen, s, NULL);
|
||||
qio_net_listener_add(s->listener, listen_sioc);
|
||||
qemu_set_info_str(&d->nc, "listening");
|
||||
net_socket_rs_init(&d->rs, net_stream_data_rs_finalize, false);
|
||||
qio_net_listener_set_client_func(d->listener, d->listen, d,
|
||||
NULL);
|
||||
qio_net_listener_add(d->listener, listen_sioc);
|
||||
}
|
||||
|
||||
static int net_stream_server_init(NetClientState *peer,
|
||||
|
|
@ -307,16 +175,18 @@ static int net_stream_server_init(NetClientState *peer,
|
|||
Error **errp)
|
||||
{
|
||||
NetClientState *nc;
|
||||
NetStreamState *s;
|
||||
NetStreamData *d;
|
||||
QIOChannelSocket *listen_sioc = qio_channel_socket_new();
|
||||
|
||||
nc = qemu_new_net_client(&net_stream_info, peer, model, name);
|
||||
s = DO_UPCAST(NetStreamState, nc, nc);
|
||||
qemu_set_info_str(&s->nc, "initializing");
|
||||
d = DO_UPCAST(NetStreamData, nc, nc);
|
||||
d->send = net_stream_send;
|
||||
d->listen = net_stream_listen;
|
||||
qemu_set_info_str(&d->nc, "initializing");
|
||||
|
||||
s->listen_ioc = QIO_CHANNEL(listen_sioc);
|
||||
d->listen_ioc = QIO_CHANNEL(listen_sioc);
|
||||
qio_channel_socket_listen_async(listen_sioc, addr, 0,
|
||||
net_stream_server_listening, s,
|
||||
net_stream_server_listening, d,
|
||||
NULL, NULL);
|
||||
|
||||
return 0;
|
||||
|
|
@ -325,49 +195,23 @@ static int net_stream_server_init(NetClientState *peer,
|
|||
static void net_stream_client_connected(QIOTask *task, gpointer opaque)
|
||||
{
|
||||
NetStreamState *s = opaque;
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(s->ioc);
|
||||
NetStreamData *d = &s->data;
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(d->ioc);
|
||||
SocketAddress *addr;
|
||||
gchar *uri;
|
||||
int ret;
|
||||
Error *err = NULL;
|
||||
|
||||
if (qio_task_propagate_error(task, &err)) {
|
||||
qemu_set_info_str(&s->nc, "error: %s", error_get_pretty(err));
|
||||
error_free(err);
|
||||
goto error;
|
||||
if (net_stream_data_client_connected(task, d) == -1) {
|
||||
net_stream_arm_reconnect(s);
|
||||
return;
|
||||
}
|
||||
|
||||
addr = qio_channel_socket_get_remote_address(sioc, NULL);
|
||||
g_assert(addr != NULL);
|
||||
uri = socket_uri(addr);
|
||||
qemu_set_info_str(&s->nc, "%s", uri);
|
||||
qemu_set_info_str(&d->nc, "%s", uri);
|
||||
g_free(uri);
|
||||
|
||||
ret = qemu_socket_try_set_nonblock(sioc->fd);
|
||||
if (addr->type == SOCKET_ADDRESS_TYPE_FD && ret < 0) {
|
||||
qemu_set_info_str(&s->nc, "can't use file descriptor %s (errno %d)",
|
||||
addr->u.fd.str, -ret);
|
||||
qapi_free_SocketAddress(addr);
|
||||
goto error;
|
||||
}
|
||||
g_assert(ret == 0);
|
||||
|
||||
net_socket_rs_init(&s->rs, net_stream_rs_finalize, false);
|
||||
|
||||
/* Disable Nagle algorithm on TCP sockets to reduce latency */
|
||||
qio_channel_set_delay(s->ioc, false);
|
||||
|
||||
s->ioc_read_tag = qio_channel_add_watch(s->ioc, G_IO_IN, net_stream_send,
|
||||
s, NULL);
|
||||
s->nc.link_down = false;
|
||||
qapi_event_send_netdev_stream_connected(s->nc.name, addr);
|
||||
qapi_event_send_netdev_stream_connected(d->nc.name, addr);
|
||||
qapi_free_SocketAddress(addr);
|
||||
|
||||
return;
|
||||
error:
|
||||
object_unref(OBJECT(s->ioc));
|
||||
s->ioc = NULL;
|
||||
net_stream_arm_reconnect(s);
|
||||
}
|
||||
|
||||
static gboolean net_stream_reconnect(gpointer data)
|
||||
|
|
@ -378,7 +222,7 @@ static gboolean net_stream_reconnect(gpointer data)
|
|||
s->timer_tag = 0;
|
||||
|
||||
sioc = qio_channel_socket_new();
|
||||
s->ioc = QIO_CHANNEL(sioc);
|
||||
s->data.ioc = QIO_CHANNEL(sioc);
|
||||
qio_channel_socket_connect_async(sioc, s->addr,
|
||||
net_stream_client_connected, s,
|
||||
NULL, NULL);
|
||||
|
|
@ -388,7 +232,7 @@ static gboolean net_stream_reconnect(gpointer data)
|
|||
static void net_stream_arm_reconnect(NetStreamState *s)
|
||||
{
|
||||
if (s->reconnect_ms && s->timer_tag == 0) {
|
||||
qemu_set_info_str(&s->nc, "connecting");
|
||||
qemu_set_info_str(&s->data.nc, "connecting");
|
||||
s->timer_tag = g_timeout_add(s->reconnect_ms, net_stream_reconnect, s);
|
||||
}
|
||||
}
|
||||
|
|
@ -405,11 +249,13 @@ static int net_stream_client_init(NetClientState *peer,
|
|||
QIOChannelSocket *sioc = qio_channel_socket_new();
|
||||
|
||||
nc = qemu_new_net_client(&net_stream_info, peer, model, name);
|
||||
s = DO_UPCAST(NetStreamState, nc, nc);
|
||||
qemu_set_info_str(&s->nc, "connecting");
|
||||
s = DO_UPCAST(NetStreamState, data.nc, nc);
|
||||
qemu_set_info_str(&s->data.nc, "connecting");
|
||||
|
||||
s->ioc = QIO_CHANNEL(sioc);
|
||||
s->nc.link_down = true;
|
||||
s->data.ioc = QIO_CHANNEL(sioc);
|
||||
s->data.nc.link_down = true;
|
||||
s->data.send = net_stream_send;
|
||||
s->data.listen = net_stream_listen;
|
||||
|
||||
s->reconnect_ms = reconnect_ms;
|
||||
if (reconnect_ms) {
|
||||
|
|
|
|||
193
net/stream_data.c
Normal file
193
net/stream_data.c
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* net stream generic functions
|
||||
*
|
||||
* Copyright Red Hat
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/iov.h"
|
||||
#include "qapi/error.h"
|
||||
#include "net/net.h"
|
||||
#include "io/channel.h"
|
||||
#include "io/net-listener.h"
|
||||
|
||||
#include "stream_data.h"
|
||||
|
||||
static gboolean net_stream_data_writable(QIOChannel *ioc,
|
||||
GIOCondition condition, gpointer data)
|
||||
{
|
||||
NetStreamData *d = data;
|
||||
|
||||
d->ioc_write_tag = 0;
|
||||
|
||||
qemu_flush_queued_packets(&d->nc);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
ssize_t net_stream_data_receive(NetStreamData *d, const uint8_t *buf,
|
||||
size_t size)
|
||||
{
|
||||
uint32_t len = htonl(size);
|
||||
struct iovec iov[] = {
|
||||
{
|
||||
.iov_base = &len,
|
||||
.iov_len = sizeof(len),
|
||||
}, {
|
||||
.iov_base = (void *)buf,
|
||||
.iov_len = size,
|
||||
},
|
||||
};
|
||||
struct iovec local_iov[2];
|
||||
unsigned int nlocal_iov;
|
||||
size_t remaining;
|
||||
ssize_t ret;
|
||||
|
||||
remaining = iov_size(iov, 2) - d->send_index;
|
||||
nlocal_iov = iov_copy(local_iov, 2, iov, 2, d->send_index, remaining);
|
||||
ret = qio_channel_writev(d->ioc, local_iov, nlocal_iov, NULL);
|
||||
if (ret == QIO_CHANNEL_ERR_BLOCK) {
|
||||
ret = 0; /* handled further down */
|
||||
}
|
||||
if (ret == -1) {
|
||||
d->send_index = 0;
|
||||
return -errno;
|
||||
}
|
||||
if (ret < (ssize_t)remaining) {
|
||||
d->send_index += ret;
|
||||
d->ioc_write_tag = qio_channel_add_watch(d->ioc, G_IO_OUT,
|
||||
net_stream_data_writable, d,
|
||||
NULL);
|
||||
return 0;
|
||||
}
|
||||
d->send_index = 0;
|
||||
return size;
|
||||
}
|
||||
|
||||
static void net_stream_data_send_completed(NetClientState *nc, ssize_t len)
|
||||
{
|
||||
NetStreamData *d = DO_UPCAST(NetStreamData, nc, nc);
|
||||
|
||||
if (!d->ioc_read_tag) {
|
||||
d->ioc_read_tag = qio_channel_add_watch(d->ioc, G_IO_IN, d->send, d,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void net_stream_data_rs_finalize(SocketReadState *rs)
|
||||
{
|
||||
NetStreamData *d = container_of(rs, NetStreamData, rs);
|
||||
|
||||
if (qemu_send_packet_async(&d->nc, rs->buf,
|
||||
rs->packet_len,
|
||||
net_stream_data_send_completed) == 0) {
|
||||
if (d->ioc_read_tag) {
|
||||
g_source_remove(d->ioc_read_tag);
|
||||
d->ioc_read_tag = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gboolean net_stream_data_send(QIOChannel *ioc, GIOCondition condition,
|
||||
NetStreamData *d)
|
||||
{
|
||||
int size;
|
||||
int ret;
|
||||
QEMU_UNINITIALIZED char buf1[NET_BUFSIZE];
|
||||
const char *buf;
|
||||
|
||||
size = qio_channel_read(d->ioc, buf1, sizeof(buf1), NULL);
|
||||
if (size < 0) {
|
||||
if (errno != EWOULDBLOCK) {
|
||||
goto eoc;
|
||||
}
|
||||
} else if (size == 0) {
|
||||
/* end of connection */
|
||||
eoc:
|
||||
d->ioc_read_tag = 0;
|
||||
if (d->ioc_write_tag) {
|
||||
g_source_remove(d->ioc_write_tag);
|
||||
d->ioc_write_tag = 0;
|
||||
}
|
||||
if (d->listener) {
|
||||
qemu_set_info_str(&d->nc, "listening");
|
||||
qio_net_listener_set_client_func(d->listener,
|
||||
d->listen, d, NULL);
|
||||
}
|
||||
object_unref(OBJECT(d->ioc));
|
||||
d->ioc = NULL;
|
||||
|
||||
net_socket_rs_init(&d->rs, net_stream_data_rs_finalize, false);
|
||||
d->nc.link_down = true;
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
buf = buf1;
|
||||
|
||||
ret = net_fill_rstate(&d->rs, (const uint8_t *)buf, size);
|
||||
|
||||
if (ret == -1) {
|
||||
goto eoc;
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
void net_stream_data_listen(QIONetListener *listener,
|
||||
QIOChannelSocket *cioc,
|
||||
NetStreamData *d)
|
||||
{
|
||||
object_ref(OBJECT(cioc));
|
||||
|
||||
qio_net_listener_set_client_func(d->listener, NULL, d, NULL);
|
||||
|
||||
d->ioc = QIO_CHANNEL(cioc);
|
||||
qio_channel_set_name(d->ioc, "stream-server");
|
||||
d->nc.link_down = false;
|
||||
|
||||
d->ioc_read_tag = qio_channel_add_watch(d->ioc, G_IO_IN, d->send, d, NULL);
|
||||
}
|
||||
|
||||
int net_stream_data_client_connected(QIOTask *task, NetStreamData *d)
|
||||
{
|
||||
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(d->ioc);
|
||||
SocketAddress *addr;
|
||||
int ret;
|
||||
Error *err = NULL;
|
||||
|
||||
if (qio_task_propagate_error(task, &err)) {
|
||||
qemu_set_info_str(&d->nc, "error: %s", error_get_pretty(err));
|
||||
error_free(err);
|
||||
goto error;
|
||||
}
|
||||
|
||||
addr = qio_channel_socket_get_remote_address(sioc, NULL);
|
||||
g_assert(addr != NULL);
|
||||
|
||||
ret = qemu_socket_try_set_nonblock(sioc->fd);
|
||||
if (addr->type == SOCKET_ADDRESS_TYPE_FD && ret < 0) {
|
||||
qemu_set_info_str(&d->nc, "can't use file descriptor %s (errno %d)",
|
||||
addr->u.fd.str, -ret);
|
||||
qapi_free_SocketAddress(addr);
|
||||
goto error;
|
||||
}
|
||||
g_assert(ret == 0);
|
||||
qapi_free_SocketAddress(addr);
|
||||
|
||||
net_socket_rs_init(&d->rs, net_stream_data_rs_finalize, false);
|
||||
|
||||
/* Disable Nagle algorithm on TCP sockets to reduce latency */
|
||||
qio_channel_set_delay(d->ioc, false);
|
||||
|
||||
d->ioc_read_tag = qio_channel_add_watch(d->ioc, G_IO_IN, d->send, d, NULL);
|
||||
d->nc.link_down = false;
|
||||
|
||||
return 0;
|
||||
error:
|
||||
object_unref(OBJECT(d->ioc));
|
||||
d->ioc = NULL;
|
||||
|
||||
return -1;
|
||||
}
|
||||
31
net/stream_data.h
Normal file
31
net/stream_data.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* net stream generic functions
|
||||
*
|
||||
* Copyright Red Hat
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
typedef struct NetStreamData {
|
||||
NetClientState nc;
|
||||
QIOChannel *ioc;
|
||||
guint ioc_read_tag;
|
||||
guint ioc_write_tag;
|
||||
SocketReadState rs;
|
||||
unsigned int send_index; /* number of bytes sent*/
|
||||
QIOChannelFunc send;
|
||||
/* server data */
|
||||
QIOChannel *listen_ioc;
|
||||
QIONetListener *listener;
|
||||
QIONetListenerClientFunc listen;
|
||||
} NetStreamData;
|
||||
|
||||
ssize_t net_stream_data_receive(NetStreamData *d, const uint8_t *buf,
|
||||
size_t size);
|
||||
void net_stream_data_rs_finalize(SocketReadState *rs);
|
||||
gboolean net_stream_data_send(QIOChannel *ioc, GIOCondition condition,
|
||||
NetStreamData *d);
|
||||
int net_stream_data_client_connected(QIOTask *task, NetStreamData *d);
|
||||
void net_stream_data_listen(QIONetListener *listener,
|
||||
QIOChannelSocket *cioc,
|
||||
NetStreamData *d);
|
||||
|
|
@ -704,11 +704,6 @@ static void tap_win32_send(void *opaque)
|
|||
}
|
||||
}
|
||||
|
||||
struct vhost_net *tap_get_vhost_net(NetClientState *nc)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static NetClientInfo net_tap_win32_info = {
|
||||
.type = NET_CLIENT_DRIVER_TAP,
|
||||
.size = sizeof(TAPState),
|
||||
|
|
|
|||
43
net/tap.c
43
net/tap.c
|
|
@ -42,11 +42,29 @@
|
|||
#include "qemu/error-report.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "hw/virtio/vhost.h"
|
||||
|
||||
#include "net/tap.h"
|
||||
|
||||
#include "net/vhost_net.h"
|
||||
|
||||
static const int kernel_feature_bits[] = {
|
||||
VIRTIO_F_NOTIFY_ON_EMPTY,
|
||||
VIRTIO_RING_F_INDIRECT_DESC,
|
||||
VIRTIO_RING_F_EVENT_IDX,
|
||||
VIRTIO_NET_F_MRG_RXBUF,
|
||||
VIRTIO_F_VERSION_1,
|
||||
VIRTIO_NET_F_MTU,
|
||||
VIRTIO_F_IOMMU_PLATFORM,
|
||||
VIRTIO_F_RING_PACKED,
|
||||
VIRTIO_F_RING_RESET,
|
||||
VIRTIO_F_IN_ORDER,
|
||||
VIRTIO_F_NOTIFICATION_DATA,
|
||||
VIRTIO_NET_F_RSC_EXT,
|
||||
VIRTIO_NET_F_HASH_REPORT,
|
||||
VHOST_INVALID_FEATURE_BIT
|
||||
};
|
||||
|
||||
typedef struct TAPState {
|
||||
NetClientState nc;
|
||||
int fd;
|
||||
|
|
@ -329,6 +347,18 @@ int tap_get_fd(NetClientState *nc)
|
|||
return s->fd;
|
||||
}
|
||||
|
||||
/*
|
||||
* tap_get_vhost_net() can return NULL if a tap net-device backend is
|
||||
* created with 'vhost=off' option, 'vhostforce=off' or no vhost or
|
||||
* vhostforce or vhostfd options at all. Please see net_init_tap_one().
|
||||
*/
|
||||
static VHostNetState *tap_get_vhost_net(NetClientState *nc)
|
||||
{
|
||||
TAPState *s = DO_UPCAST(TAPState, nc, nc);
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_TAP);
|
||||
return s->vhost_net;
|
||||
}
|
||||
|
||||
/* fd support */
|
||||
|
||||
static NetClientInfo net_tap_info = {
|
||||
|
|
@ -347,6 +377,7 @@ static NetClientInfo net_tap_info = {
|
|||
.set_vnet_le = tap_set_vnet_le,
|
||||
.set_vnet_be = tap_set_vnet_be,
|
||||
.set_steering_ebpf = tap_set_steering_ebpf,
|
||||
.get_vhost_net = tap_get_vhost_net,
|
||||
};
|
||||
|
||||
static TAPState *net_tap_fd_init(NetClientState *peer,
|
||||
|
|
@ -712,6 +743,11 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,
|
|||
}
|
||||
options.opaque = (void *)(uintptr_t)vhostfd;
|
||||
options.nvqs = 2;
|
||||
options.feature_bits = kernel_feature_bits;
|
||||
options.get_acked_features = NULL;
|
||||
options.save_acked_features = NULL;
|
||||
options.max_tx_queue_size = 0;
|
||||
options.is_vhost_user = false;
|
||||
|
||||
s->vhost_net = vhost_net_init(&options);
|
||||
if (!s->vhost_net) {
|
||||
|
|
@ -980,13 +1016,6 @@ free_fail:
|
|||
return 0;
|
||||
}
|
||||
|
||||
VHostNetState *tap_get_vhost_net(NetClientState *nc)
|
||||
{
|
||||
TAPState *s = DO_UPCAST(TAPState, nc, nc);
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_TAP);
|
||||
return s->vhost_net;
|
||||
}
|
||||
|
||||
int tap_enable(NetClientState *nc)
|
||||
{
|
||||
TAPState *s = DO_UPCAST(TAPState, nc, nc);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
#include "qemu/osdep.h"
|
||||
#include "clients.h"
|
||||
#include "net/vhost_net.h"
|
||||
#include "net/vhost-user.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@
|
|||
#include "qemu/osdep.h"
|
||||
#include "clients.h"
|
||||
#include "net/vhost_net.h"
|
||||
#include "net/vhost-user.h"
|
||||
#include "hw/virtio/vhost.h"
|
||||
#include "hw/virtio/vhost-user.h"
|
||||
#include "standard-headers/linux/virtio_net.h"
|
||||
#include "chardev/char-fe.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qapi-commands-net.h"
|
||||
|
|
@ -22,6 +23,46 @@
|
|||
#include "qemu/option.h"
|
||||
#include "trace.h"
|
||||
|
||||
static const int user_feature_bits[] = {
|
||||
VIRTIO_F_NOTIFY_ON_EMPTY,
|
||||
VIRTIO_F_NOTIFICATION_DATA,
|
||||
VIRTIO_RING_F_INDIRECT_DESC,
|
||||
VIRTIO_RING_F_EVENT_IDX,
|
||||
|
||||
VIRTIO_F_ANY_LAYOUT,
|
||||
VIRTIO_F_VERSION_1,
|
||||
VIRTIO_NET_F_CSUM,
|
||||
VIRTIO_NET_F_GUEST_CSUM,
|
||||
VIRTIO_NET_F_GSO,
|
||||
VIRTIO_NET_F_GUEST_TSO4,
|
||||
VIRTIO_NET_F_GUEST_TSO6,
|
||||
VIRTIO_NET_F_GUEST_ECN,
|
||||
VIRTIO_NET_F_GUEST_UFO,
|
||||
VIRTIO_NET_F_HOST_TSO4,
|
||||
VIRTIO_NET_F_HOST_TSO6,
|
||||
VIRTIO_NET_F_HOST_ECN,
|
||||
VIRTIO_NET_F_HOST_UFO,
|
||||
VIRTIO_NET_F_MRG_RXBUF,
|
||||
VIRTIO_NET_F_MTU,
|
||||
VIRTIO_F_IOMMU_PLATFORM,
|
||||
VIRTIO_F_RING_PACKED,
|
||||
VIRTIO_F_RING_RESET,
|
||||
VIRTIO_F_IN_ORDER,
|
||||
VIRTIO_NET_F_RSS,
|
||||
VIRTIO_NET_F_RSC_EXT,
|
||||
VIRTIO_NET_F_HASH_REPORT,
|
||||
VIRTIO_NET_F_GUEST_USO4,
|
||||
VIRTIO_NET_F_GUEST_USO6,
|
||||
VIRTIO_NET_F_HOST_USO,
|
||||
|
||||
/* This bit implies RARP isn't sent by QEMU out of band */
|
||||
VIRTIO_NET_F_GUEST_ANNOUNCE,
|
||||
|
||||
VIRTIO_NET_F_MQ,
|
||||
|
||||
VHOST_INVALID_FEATURE_BIT
|
||||
};
|
||||
|
||||
typedef struct NetVhostUserState {
|
||||
NetClientState nc;
|
||||
CharBackend chr; /* only queue index 0 */
|
||||
|
|
@ -32,21 +73,21 @@ typedef struct NetVhostUserState {
|
|||
bool started;
|
||||
} NetVhostUserState;
|
||||
|
||||
VHostNetState *vhost_user_get_vhost_net(NetClientState *nc)
|
||||
static struct vhost_net *vhost_user_get_vhost_net(NetClientState *nc)
|
||||
{
|
||||
NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
|
||||
return s->vhost_net;
|
||||
}
|
||||
|
||||
uint64_t vhost_user_get_acked_features(NetClientState *nc)
|
||||
static uint64_t vhost_user_get_acked_features(NetClientState *nc)
|
||||
{
|
||||
NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
|
||||
return s->acked_features;
|
||||
}
|
||||
|
||||
void vhost_user_save_acked_features(NetClientState *nc)
|
||||
static void vhost_user_save_acked_features(NetClientState *nc)
|
||||
{
|
||||
NetVhostUserState *s;
|
||||
|
||||
|
|
@ -96,6 +137,12 @@ static int vhost_user_start(int queues, NetClientState *ncs[],
|
|||
options.opaque = be;
|
||||
options.busyloop_timeout = 0;
|
||||
options.nvqs = 2;
|
||||
options.feature_bits = user_feature_bits;
|
||||
options.max_tx_queue_size = VIRTQUEUE_MAX_SIZE;
|
||||
options.get_acked_features = vhost_user_get_acked_features;
|
||||
options.save_acked_features = vhost_user_save_acked_features;
|
||||
options.is_vhost_user = true;
|
||||
|
||||
net = vhost_net_init(&options);
|
||||
if (!net) {
|
||||
error_report("failed to init vhost_net for queue %d", i);
|
||||
|
|
@ -231,6 +278,7 @@ static NetClientInfo net_vhost_user_info = {
|
|||
.set_vnet_be = vhost_user_set_vnet_endianness,
|
||||
.set_vnet_le = vhost_user_set_vnet_endianness,
|
||||
.check_peer_type = vhost_user_check_peer_type,
|
||||
.get_vhost_net = vhost_user_get_vhost_net,
|
||||
};
|
||||
|
||||
static gboolean net_vhost_user_watch(void *do_not_use, GIOCondition cond,
|
||||
|
|
@ -264,7 +312,7 @@ static void chr_closed_bh(void *opaque)
|
|||
vhost_user_save_acked_features(ncs[i]);
|
||||
}
|
||||
|
||||
qmp_set_link(name, false, &err);
|
||||
net_client_set_link(ncs, queues, false);
|
||||
|
||||
qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, net_vhost_user_event,
|
||||
NULL, opaque, NULL, true);
|
||||
|
|
@ -300,7 +348,7 @@ static void net_vhost_user_event(void *opaque, QEMUChrEvent event)
|
|||
}
|
||||
s->watch = qemu_chr_fe_add_watch(&s->chr, G_IO_HUP,
|
||||
net_vhost_user_watch, s);
|
||||
qmp_set_link(name, true, &err);
|
||||
net_client_set_link(ncs, queues, true);
|
||||
s->started = true;
|
||||
qapi_event_send_netdev_vhost_user_connected(name, chr->label);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ typedef struct VhostVDPAState {
|
|||
* with the exception of VHOST_INVALID_FEATURE_BIT,
|
||||
* which should always be the last entry.
|
||||
*/
|
||||
const int vdpa_feature_bits[] = {
|
||||
static const int vdpa_feature_bits[] = {
|
||||
VIRTIO_F_ANY_LAYOUT,
|
||||
VIRTIO_F_IOMMU_PLATFORM,
|
||||
VIRTIO_F_NOTIFY_ON_EMPTY,
|
||||
|
|
@ -132,7 +132,7 @@ static const uint64_t vdpa_svq_device_features =
|
|||
|
||||
#define VHOST_VDPA_NET_CVQ_ASID 1
|
||||
|
||||
VHostNetState *vhost_vdpa_get_vhost_net(NetClientState *nc)
|
||||
static struct vhost_net *vhost_vdpa_get_vhost_net(NetClientState *nc)
|
||||
{
|
||||
VhostVDPAState *s = DO_UPCAST(VhostVDPAState, nc, nc);
|
||||
assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA);
|
||||
|
|
@ -201,6 +201,11 @@ static int vhost_vdpa_add(NetClientState *ncs, void *be,
|
|||
options.opaque = be;
|
||||
options.busyloop_timeout = 0;
|
||||
options.nvqs = nvqs;
|
||||
options.feature_bits = vdpa_feature_bits;
|
||||
options.get_acked_features = NULL;
|
||||
options.save_acked_features = NULL;
|
||||
options.max_tx_queue_size = VIRTQUEUE_MAX_SIZE;
|
||||
options.is_vhost_user = false;
|
||||
|
||||
net = vhost_net_init(&options);
|
||||
if (!net) {
|
||||
|
|
@ -432,6 +437,7 @@ static NetClientInfo net_vhost_vdpa_info = {
|
|||
.set_vnet_le = vhost_vdpa_set_vnet_le,
|
||||
.check_peer_type = vhost_vdpa_check_peer_type,
|
||||
.set_steering_ebpf = vhost_vdpa_set_steering_ebpf,
|
||||
.get_vhost_net = vhost_vdpa_get_vhost_net,
|
||||
};
|
||||
|
||||
static int64_t vhost_vdpa_get_vring_group(int device_fd, unsigned vq_index,
|
||||
|
|
@ -1287,6 +1293,7 @@ static NetClientInfo net_vhost_vdpa_cvq_info = {
|
|||
.has_ufo = vhost_vdpa_has_ufo,
|
||||
.check_peer_type = vhost_vdpa_check_peer_type,
|
||||
.set_steering_ebpf = vhost_vdpa_set_steering_ebpf,
|
||||
.get_vhost_net = vhost_vdpa_get_vhost_net,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
|||
118
qapi/net.json
118
qapi/net.json
|
|
@ -112,6 +112,119 @@
|
|||
'data': {
|
||||
'str': 'str' } }
|
||||
|
||||
##
|
||||
# @NetDevPasstOptions:
|
||||
#
|
||||
# Unprivileged user-mode network connectivity using passt
|
||||
#
|
||||
# @path: Filename of the passt program to run (by default 'passt', and use PATH)
|
||||
#
|
||||
# @quiet: don't print informational messages (default, passed as '--quiet')
|
||||
#
|
||||
# @vhost-user: enable vhost-user
|
||||
#
|
||||
# @mtu: assign MTU via DHCP/NDP
|
||||
#
|
||||
# @address: IPv4 or IPv6 address
|
||||
#
|
||||
# @netmask: IPv4 mask
|
||||
#
|
||||
# @mac: source MAC address
|
||||
#
|
||||
# @gateway: IPv4 or IPv6 address as gateway
|
||||
#
|
||||
# @interface: interface for addresses and routes
|
||||
#
|
||||
# @outbound: bind to address as outbound source
|
||||
#
|
||||
# @outbound-if4: bind to outbound interface for IPv4
|
||||
#
|
||||
# @outbound-if6: bind to outbound interface for IPv6
|
||||
#
|
||||
# @dns: IPv4 or IPv6 address as DNS
|
||||
#
|
||||
# @search: search domains
|
||||
#
|
||||
# @fqdn: FQDN to configure client with
|
||||
#
|
||||
# @dhcp-dns: enable/disable DNS list in DHCP/DHCPv6/NDP
|
||||
#
|
||||
# @dhcp-search: enable/disable list in DHCP/DHCPv6/NDP
|
||||
#
|
||||
# @map-host-loopback: addresse to refer to host
|
||||
#
|
||||
# @map-guest-addr: addr to translate to guest's address
|
||||
#
|
||||
# @dns-forward: forward DNS queries sent to
|
||||
#
|
||||
# @dns-host: host nameserver to direct queries to
|
||||
#
|
||||
# @tcp: enable/disable TCP
|
||||
#
|
||||
# @udp: enable/disable UDP
|
||||
#
|
||||
# @icmp: enable/disable ICMP
|
||||
#
|
||||
# @dhcp: enable/disable DHCP
|
||||
#
|
||||
# @ndp: enable/disable NDP
|
||||
#
|
||||
# @dhcpv6: enable/disable DHCPv6
|
||||
#
|
||||
# @ra: enable/disable route advertisements
|
||||
#
|
||||
# @freebind: bind to any address for forwarding
|
||||
#
|
||||
# @ipv4: enable/disable IPv4
|
||||
#
|
||||
# @ipv6: enable/disable IPv6
|
||||
#
|
||||
# @tcp-ports: TCP ports to forward
|
||||
#
|
||||
# @udp-ports: UDP ports to forward
|
||||
#
|
||||
# @param: parameter to pass to passt command
|
||||
#
|
||||
# Since: 10.1
|
||||
##
|
||||
{ 'struct': 'NetDevPasstOptions',
|
||||
'data': {
|
||||
'*path': 'str',
|
||||
'*quiet': 'bool',
|
||||
'*vhost-user': 'bool',
|
||||
'*mtu': 'int',
|
||||
'*address': 'str',
|
||||
'*netmask': 'str',
|
||||
'*mac': 'str',
|
||||
'*gateway': 'str',
|
||||
'*interface': 'str',
|
||||
'*outbound': 'str',
|
||||
'*outbound-if4': 'str',
|
||||
'*outbound-if6': 'str',
|
||||
'*dns': 'str',
|
||||
'*search': ['String'],
|
||||
'*fqdn': 'str',
|
||||
'*dhcp-dns': 'bool',
|
||||
'*dhcp-search': 'bool',
|
||||
'*map-host-loopback': 'str',
|
||||
'*map-guest-addr': 'str',
|
||||
'*dns-forward': 'str',
|
||||
'*dns-host': 'str',
|
||||
'*tcp': 'bool',
|
||||
'*udp': 'bool',
|
||||
'*icmp': 'bool',
|
||||
'*dhcp': 'bool',
|
||||
'*ndp': 'bool',
|
||||
'*dhcpv6': 'bool',
|
||||
'*ra': 'bool',
|
||||
'*freebind': 'bool',
|
||||
'*ipv4': 'bool',
|
||||
'*ipv6': 'bool',
|
||||
'*tcp-ports': ['String'],
|
||||
'*udp-ports': ['String'],
|
||||
'*param': ['String'] },
|
||||
'if': 'CONFIG_PASST' }
|
||||
|
||||
##
|
||||
# @NetdevUserOptions:
|
||||
#
|
||||
|
|
@ -729,12 +842,15 @@
|
|||
#
|
||||
# @af-xdp: since 8.2
|
||||
#
|
||||
# @passt: since 10.1
|
||||
#
|
||||
# Since: 2.7
|
||||
##
|
||||
{ 'enum': 'NetClientDriver',
|
||||
'data': [ 'none', 'nic', 'user', 'tap', 'l2tpv3', 'socket', 'stream',
|
||||
'dgram', 'vde', 'bridge', 'hubport', 'netmap', 'vhost-user',
|
||||
'vhost-vdpa',
|
||||
{ 'name': 'passt', 'if': 'CONFIG_PASST' },
|
||||
{ 'name': 'af-xdp', 'if': 'CONFIG_AF_XDP' },
|
||||
{ 'name': 'vmnet-host', 'if': 'CONFIG_VMNET' },
|
||||
{ 'name': 'vmnet-shared', 'if': 'CONFIG_VMNET' },
|
||||
|
|
@ -756,6 +872,8 @@
|
|||
'discriminator': 'type',
|
||||
'data': {
|
||||
'nic': 'NetLegacyNicOptions',
|
||||
'passt': { 'type': 'NetDevPasstOptions',
|
||||
'if': 'CONFIG_PASST' },
|
||||
'user': 'NetdevUserOptions',
|
||||
'tap': 'NetdevTapOptions',
|
||||
'l2tpv3': 'NetdevL2TPv3Options',
|
||||
|
|
|
|||
153
qemu-options.hx
153
qemu-options.hx
|
|
@ -2796,6 +2796,26 @@ DEFHEADING()
|
|||
DEFHEADING(Network options:)
|
||||
|
||||
DEF("netdev", HAS_ARG, QEMU_OPTION_netdev,
|
||||
#ifdef CONFIG_PASST
|
||||
"-netdev passt,id=str[,path=file][,quiet=on|off][,vhost-user=on|off]\n"
|
||||
"[,mtu=mtu][,address=addr][,netmask=mask][,mac=addr][,gateway=addr]\n"
|
||||
" [,interface=name][,outbound=address][,outbound-if4=name]\n"
|
||||
" [,outbound-if6=name][,dns=addr][,search=list][,fqdn=name]\n"
|
||||
" [,dhcp-dns=on|off][,dhcp-search=on|off][,map-host-loopback=addr]\n"
|
||||
" [,map-guest-addr=addr][,dns-forward=addr][,dns-host=addr]\n"
|
||||
" [,tcp=on|off][,udp=on|off][,icmp=on|off][,dhcp=on|off]\n"
|
||||
" [,ndp=on|off][,dhcpv6=on|off][,ra=on|off][,freebind=on|off]\n"
|
||||
" [,ipv4=on|off][,ipv6=on|off][,tcp-ports=spec][,udp-ports=spec]\n"
|
||||
" [,param=list]\n"
|
||||
" configure a passt network backend with ID 'str'\n"
|
||||
" if 'path' is not provided 'passt' will be started according to PATH\n"
|
||||
" by default, informational message of passt are not displayed (quiet=on)\n"
|
||||
" to display this message, use 'quiet=off'\n"
|
||||
" by default, passt will be started in socket-based mode, to enable vhost-mode,\n"
|
||||
" use 'vhost-user=on'\n"
|
||||
" for details on other options, refer to passt(1)\n"
|
||||
" 'param' allows to pass any option defined by passt(1)\n"
|
||||
#endif
|
||||
#ifdef CONFIG_SLIRP
|
||||
"-netdev user,id=str[,ipv4=on|off][,net=addr[/mask]][,host=addr]\n"
|
||||
" [,ipv6=on|off][,ipv6-net=addr[/int]][,ipv6-host=addr]\n"
|
||||
|
|
@ -2952,6 +2972,9 @@ DEF("netdev", HAS_ARG, QEMU_OPTION_netdev,
|
|||
" configure a hub port on the hub with ID 'n'\n", QEMU_ARCH_ALL)
|
||||
DEF("nic", HAS_ARG, QEMU_OPTION_nic,
|
||||
"-nic [tap|bridge|"
|
||||
#ifdef CONFIG_PASST
|
||||
"passt|"
|
||||
#endif
|
||||
#ifdef CONFIG_SLIRP
|
||||
"user|"
|
||||
#endif
|
||||
|
|
@ -2984,6 +3007,9 @@ DEF("net", HAS_ARG, QEMU_OPTION_net,
|
|||
" configure or create an on-board (or machine default) NIC and\n"
|
||||
" connect it to hub 0 (please use -nic unless you need a hub)\n"
|
||||
"-net ["
|
||||
#ifdef CONFIG_PASST
|
||||
"passt|"
|
||||
#endif
|
||||
#ifdef CONFIG_SLIRP
|
||||
"user|"
|
||||
#endif
|
||||
|
|
@ -3005,7 +3031,7 @@ DEF("net", HAS_ARG, QEMU_OPTION_net,
|
|||
" old way to initialize a host network interface\n"
|
||||
" (use the -netdev option if possible instead)\n", QEMU_ARCH_ALL)
|
||||
SRST
|
||||
``-nic [tap|bridge|user|l2tpv3|vde|netmap|af-xdp|vhost-user|socket][,...][,mac=macaddr][,model=mn]``
|
||||
``-nic [tap|passt|bridge|user|l2tpv3|vde|netmap|af-xdp|vhost-user|socket][,...][,mac=macaddr][,model=mn]``
|
||||
This option is a shortcut for configuring both the on-board
|
||||
(default) guest NIC hardware and the host network backend in one go.
|
||||
The host backend options are the same as with the corresponding
|
||||
|
|
@ -3027,6 +3053,129 @@ SRST
|
|||
network backend) which is activated if no other networking options
|
||||
are provided.
|
||||
|
||||
``-netdev passt,id=str[,option][,...]``
|
||||
Configure a passt network backend which requires no administrator
|
||||
privilege to run. Valid options are:
|
||||
|
||||
``id=id``
|
||||
Assign symbolic name for use in monitor commands.
|
||||
|
||||
``path=file``
|
||||
Filename of the passt program to run. If it is not provided,
|
||||
passt command will be started with the help of the PATH environment
|
||||
variable.
|
||||
|
||||
``quiet=on|off``
|
||||
By default, ``quiet=on`` to disable informational message from
|
||||
passt. ``quiet=on`` is passed as ``--quiet`` to passt.
|
||||
|
||||
``vhost-user=on|off``
|
||||
By default, ``vhost-user=off`` and QEMU uses the stream network
|
||||
backend to communicate with passt. If ``vhost-user=on``, passt is
|
||||
started with ``--vhost-user`` and QEMU uses the vhost-user network
|
||||
backend to communicate with passt.
|
||||
|
||||
``@mtu``
|
||||
Assign MTU via DHCP/NDP
|
||||
|
||||
``address``
|
||||
IPv4 or IPv6 address
|
||||
|
||||
``netmask``
|
||||
IPv4 mask
|
||||
|
||||
``mac``
|
||||
source MAC address
|
||||
|
||||
``gateway``
|
||||
IPv4 or IPv6 address as gateway
|
||||
|
||||
``interface``
|
||||
Interface for addresses and routes
|
||||
|
||||
``outbound``
|
||||
Bind to address as outbound source
|
||||
|
||||
``outbound-if4``
|
||||
Bind to outbound interface for IPv4
|
||||
|
||||
``outbound-if6``
|
||||
Bind to outbound interface for IPv6
|
||||
|
||||
``dns``
|
||||
IPv4 or IPv6 address as DNS
|
||||
|
||||
``search``
|
||||
Search domains
|
||||
|
||||
``fqdn``
|
||||
FQDN to configure client with
|
||||
|
||||
``dhcp-dns``
|
||||
Enable/disable DNS list in DHCP/DHCPv6/NDP
|
||||
|
||||
``dhcp-search``
|
||||
Enable/disable list in DHCP/DHCPv6/NDP
|
||||
|
||||
``map-host-loopback``
|
||||
Addresse to refer to host
|
||||
|
||||
``map-guest-addr``
|
||||
Addr to translate to guest's address
|
||||
|
||||
``dns-forward``
|
||||
Forward DNS queries sent to
|
||||
|
||||
``dns-host``
|
||||
Host nameserver to direct queries to
|
||||
|
||||
``tcp``
|
||||
Enable/disable TCP
|
||||
|
||||
``udp``
|
||||
Enable/disable UDP
|
||||
|
||||
``icmp``
|
||||
Enable/disable ICMP
|
||||
|
||||
``dhcp``
|
||||
Enable/disable DHCP
|
||||
|
||||
``ndp``
|
||||
Enable/disable NDP
|
||||
|
||||
``dhcpv6``
|
||||
Enable/disable DHCPv6
|
||||
|
||||
``ra``
|
||||
Enable/disable route advertisements
|
||||
|
||||
``freebind``
|
||||
Bind to any address for forwarding
|
||||
|
||||
``ipv4``
|
||||
Enable/disable IPv4
|
||||
|
||||
``ipv6``
|
||||
Enable/disable IPv6
|
||||
|
||||
``tcp-ports``
|
||||
TCP ports to forward
|
||||
|
||||
``udp-ports``
|
||||
UDP ports to forward
|
||||
|
||||
``param=string``
|
||||
``string`` will be passed to passt has a command line parameter,
|
||||
we can have multiple occurences of the ``param`` parameter to
|
||||
pass multiple parameters to passt.
|
||||
|
||||
For instance, to pass ``--trace --log=trace.log``:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
|qemu_system| -nic passt,param=--trace,param=--log=trace.log
|
||||
|
||||
``-netdev user,id=id[,option][,option][,...]``
|
||||
Configure user mode host network backend which requires no
|
||||
administrator privilege to run. Valid options are:
|
||||
|
|
@ -3711,7 +3860,7 @@ SRST
|
|||
Use ``-net nic,model=help`` for a list of available devices for your
|
||||
target.
|
||||
|
||||
``-net user|tap|bridge|socket|l2tpv3|vde[,...][,name=name]``
|
||||
``-net user|passt|tap|bridge|socket|l2tpv3|vde[,...][,name=name]``
|
||||
Configure a host network backend (with the options corresponding to
|
||||
the same ``-netdev`` option) and connect it to the emulated hub 0
|
||||
(the default hub). Use name to specify the name of the hub port.
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ meson_options_help() {
|
|||
printf "%s\n" ' oss OSS sound support'
|
||||
printf "%s\n" ' pa PulseAudio sound support'
|
||||
printf "%s\n" ' parallels parallels image format support'
|
||||
printf "%s\n" ' passt passt network backend support'
|
||||
printf "%s\n" ' pipewire PipeWire sound support'
|
||||
printf "%s\n" ' pixman pixman support'
|
||||
printf "%s\n" ' plugins TCG plugins via shared library loading'
|
||||
|
|
@ -422,6 +423,8 @@ _meson_option_parse() {
|
|||
--disable-pa) printf "%s" -Dpa=disabled ;;
|
||||
--enable-parallels) printf "%s" -Dparallels=enabled ;;
|
||||
--disable-parallels) printf "%s" -Dparallels=disabled ;;
|
||||
--enable-passt) printf "%s" -Dpasst=enabled ;;
|
||||
--disable-passt) printf "%s" -Dpasst=disabled ;;
|
||||
--enable-pipewire) printf "%s" -Dpipewire=enabled ;;
|
||||
--disable-pipewire) printf "%s" -Dpipewire=disabled ;;
|
||||
--enable-pixman) printf "%s" -Dpixman=enabled ;;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue