vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
/* tcp_vu.c - TCP L2 vhost-user management functions
|
|
|
|
*
|
|
|
|
* Copyright Red Hat
|
|
|
|
* Author: Laurent Vivier <lvivier@redhat.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
|
|
|
|
#include <netinet/ip.h>
|
|
|
|
#include <netinet/tcp.h>
|
|
|
|
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
|
|
|
#include <netinet/if_ether.h>
|
|
|
|
#include <linux/virtio_net.h>
|
|
|
|
|
|
|
|
#include "util.h"
|
|
|
|
#include "ip.h"
|
|
|
|
#include "passt.h"
|
|
|
|
#include "siphash.h"
|
|
|
|
#include "inany.h"
|
|
|
|
#include "vhost_user.h"
|
|
|
|
#include "tcp.h"
|
|
|
|
#include "pcap.h"
|
|
|
|
#include "flow.h"
|
|
|
|
#include "tcp_conn.h"
|
|
|
|
#include "flow_table.h"
|
|
|
|
#include "tcp_vu.h"
|
|
|
|
#include "tap.h"
|
|
|
|
#include "tcp_internal.h"
|
|
|
|
#include "checksum.h"
|
|
|
|
#include "vu_common.h"
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
static struct iovec iov_vu[VIRTQUEUE_MAX_SIZE + 1];
|
|
|
|
static struct vu_virtq_element elem[VIRTQUEUE_MAX_SIZE];
|
|
|
|
static int head[VIRTQUEUE_MAX_SIZE + 1];
|
|
|
|
static int head_cnt;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* tcp_vu_hdrlen() - return the size of the header in level 2 frame (TCP)
|
|
|
|
* @v6: Set for IPv6 packet
|
|
|
|
*
|
|
|
|
* Return: Return the size of the header
|
|
|
|
*/
|
|
|
|
static size_t tcp_vu_hdrlen(bool v6)
|
|
|
|
{
|
|
|
|
size_t hdrlen;
|
|
|
|
|
|
|
|
hdrlen = sizeof(struct virtio_net_hdr_mrg_rxbuf) +
|
|
|
|
sizeof(struct ethhdr) + sizeof(struct tcphdr);
|
|
|
|
|
|
|
|
if (v6)
|
|
|
|
hdrlen += sizeof(struct ipv6hdr);
|
|
|
|
else
|
|
|
|
hdrlen += sizeof(struct iphdr);
|
|
|
|
|
|
|
|
return hdrlen;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* tcp_vu_send_flag() - Send segment with flags to vhost-user (no payload)
|
|
|
|
* @c: Execution context
|
|
|
|
* @conn: Connection pointer
|
|
|
|
* @flags: TCP flags: if not set, send segment only if ACK is due
|
|
|
|
*
|
|
|
|
* Return: negative error code on connection reset, 0 otherwise
|
|
|
|
*/
|
|
|
|
int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags)
|
|
|
|
{
|
|
|
|
struct vu_dev *vdev = c->vdev;
|
|
|
|
struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE];
|
2024-11-22 17:43:36 +01:00
|
|
|
size_t optlen, hdrlen;
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
struct vu_virtq_element flags_elem[2];
|
|
|
|
struct ipv6hdr *ip6h = NULL;
|
2024-11-27 14:54:09 +11:00
|
|
|
struct iphdr *ip4h = NULL;
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
struct iovec flags_iov[2];
|
2024-11-27 14:54:07 +11:00
|
|
|
struct tcp_syn_opts *opts;
|
|
|
|
struct iov_tail payload;
|
|
|
|
struct tcphdr *th;
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
struct ethhdr *eh;
|
|
|
|
uint32_t seq;
|
|
|
|
int elem_cnt;
|
|
|
|
int nb_ack;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
hdrlen = tcp_vu_hdrlen(CONN_V6(conn));
|
|
|
|
|
|
|
|
vu_set_element(&flags_elem[0], NULL, &flags_iov[0]);
|
|
|
|
|
|
|
|
elem_cnt = vu_collect(vdev, vq, &flags_elem[0], 1,
|
|
|
|
hdrlen + sizeof(struct tcp_syn_opts), NULL);
|
|
|
|
if (elem_cnt != 1)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
ASSERT(flags_elem[0].in_sg[0].iov_len >=
|
|
|
|
hdrlen + sizeof(struct tcp_syn_opts));
|
|
|
|
|
|
|
|
vu_set_vnethdr(vdev, flags_elem[0].in_sg[0].iov_base, 1);
|
|
|
|
|
|
|
|
eh = vu_eth(flags_elem[0].in_sg[0].iov_base);
|
|
|
|
|
|
|
|
memcpy(eh->h_dest, c->guest_mac, sizeof(eh->h_dest));
|
|
|
|
memcpy(eh->h_source, c->our_tap_mac, sizeof(eh->h_source));
|
|
|
|
|
|
|
|
if (CONN_V4(conn)) {
|
|
|
|
eh->h_proto = htons(ETH_P_IP);
|
|
|
|
|
2024-11-27 14:54:09 +11:00
|
|
|
ip4h = vu_ip(flags_elem[0].in_sg[0].iov_base);
|
|
|
|
*ip4h = (struct iphdr)L2_BUF_IP4_INIT(IPPROTO_TCP);
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
|
2024-11-27 14:54:07 +11:00
|
|
|
th = vu_payloadv4(flags_elem[0].in_sg[0].iov_base);
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
} else {
|
|
|
|
eh->h_proto = htons(ETH_P_IPV6);
|
|
|
|
|
|
|
|
ip6h = vu_ip(flags_elem[0].in_sg[0].iov_base);
|
|
|
|
*ip6h = (struct ipv6hdr)L2_BUF_IP6_INIT(IPPROTO_TCP);
|
2024-11-27 14:54:07 +11:00
|
|
|
th = vu_payloadv6(flags_elem[0].in_sg[0].iov_base);
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
}
|
|
|
|
|
2024-11-27 14:54:07 +11:00
|
|
|
memset(th, 0, sizeof(*th));
|
|
|
|
th->doff = sizeof(*th) / 4;
|
|
|
|
th->ack = 1;
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
|
|
|
|
seq = conn->seq_to_tap;
|
2024-11-27 14:54:07 +11:00
|
|
|
opts = (struct tcp_syn_opts *)(th + 1);
|
|
|
|
ret = tcp_prepare_flags(c, conn, flags, th, opts, &optlen);
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
if (ret <= 0) {
|
|
|
|
vu_queue_rewind(vq, 1);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-11-22 17:43:36 +01:00
|
|
|
flags_elem[0].in_sg[0].iov_len = hdrlen + optlen;
|
2024-11-27 14:54:07 +11:00
|
|
|
payload = IOV_TAIL(flags_elem[0].in_sg, 1, hdrlen);
|
2024-11-22 17:43:36 +01:00
|
|
|
|
2024-11-27 14:54:09 +11:00
|
|
|
tcp_fill_headers(conn, NULL, ip4h, ip6h, th, &payload,
|
2024-11-27 14:54:10 +11:00
|
|
|
NULL, seq, !*c->pcap);
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
|
|
|
|
if (*c->pcap) {
|
|
|
|
pcap_iov(&flags_elem[0].in_sg[0], 1,
|
|
|
|
sizeof(struct virtio_net_hdr_mrg_rxbuf));
|
|
|
|
}
|
|
|
|
nb_ack = 1;
|
|
|
|
|
|
|
|
if (flags & DUP_ACK) {
|
|
|
|
vu_set_element(&flags_elem[1], NULL, &flags_iov[1]);
|
|
|
|
|
|
|
|
elem_cnt = vu_collect(vdev, vq, &flags_elem[1], 1,
|
|
|
|
flags_elem[0].in_sg[0].iov_len, NULL);
|
|
|
|
if (elem_cnt == 1 &&
|
|
|
|
flags_elem[1].in_sg[0].iov_len >=
|
|
|
|
flags_elem[0].in_sg[0].iov_len) {
|
|
|
|
memcpy(flags_elem[1].in_sg[0].iov_base,
|
|
|
|
flags_elem[0].in_sg[0].iov_base,
|
|
|
|
flags_elem[0].in_sg[0].iov_len);
|
|
|
|
nb_ack++;
|
|
|
|
|
|
|
|
if (*c->pcap) {
|
|
|
|
pcap_iov(&flags_elem[1].in_sg[0], 1,
|
|
|
|
sizeof(struct virtio_net_hdr_mrg_rxbuf));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
vu_flush(vdev, vq, flags_elem, nb_ack);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** tcp_vu_sock_recv() - Receive datastream from socket into vhost-user buffers
|
|
|
|
* @c: Execution context
|
|
|
|
* @conn: Connection pointer
|
|
|
|
* @v6: Set for IPv6 connections
|
|
|
|
* @already_sent: Number of bytes already sent
|
|
|
|
* @fillsize: Maximum bytes to fill in guest-side receiving window
|
|
|
|
* @iov_cnt: number of iov (output)
|
|
|
|
*
|
|
|
|
* Return: Number of iov entries used to store the data or negative error code
|
|
|
|
*/
|
|
|
|
static ssize_t tcp_vu_sock_recv(const struct ctx *c,
|
|
|
|
const struct tcp_tap_conn *conn, bool v6,
|
|
|
|
uint32_t already_sent, size_t fillsize,
|
|
|
|
int *iov_cnt)
|
|
|
|
{
|
|
|
|
struct vu_dev *vdev = c->vdev;
|
|
|
|
struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE];
|
|
|
|
struct msghdr mh_sock = { 0 };
|
|
|
|
uint16_t mss = MSS_GET(conn);
|
|
|
|
int s = conn->sock;
|
|
|
|
ssize_t ret, len;
|
|
|
|
size_t hdrlen;
|
|
|
|
int elem_cnt;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
*iov_cnt = 0;
|
|
|
|
|
|
|
|
hdrlen = tcp_vu_hdrlen(v6);
|
|
|
|
|
|
|
|
vu_init_elem(elem, &iov_vu[1], VIRTQUEUE_MAX_SIZE);
|
|
|
|
|
|
|
|
elem_cnt = 0;
|
|
|
|
head_cnt = 0;
|
|
|
|
while (fillsize > 0 && elem_cnt < VIRTQUEUE_MAX_SIZE) {
|
|
|
|
struct iovec *iov;
|
|
|
|
size_t frame_size, dlen;
|
|
|
|
int cnt;
|
|
|
|
|
|
|
|
cnt = vu_collect(vdev, vq, &elem[elem_cnt],
|
|
|
|
VIRTQUEUE_MAX_SIZE - elem_cnt,
|
|
|
|
MIN(mss, fillsize) + hdrlen, &frame_size);
|
|
|
|
if (cnt == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
dlen = frame_size - hdrlen;
|
|
|
|
|
|
|
|
/* reserve space for headers in iov */
|
|
|
|
iov = &elem[elem_cnt].in_sg[0];
|
|
|
|
ASSERT(iov->iov_len >= hdrlen);
|
|
|
|
iov->iov_base = (char *)iov->iov_base + hdrlen;
|
|
|
|
iov->iov_len -= hdrlen;
|
|
|
|
head[head_cnt++] = elem_cnt;
|
|
|
|
|
|
|
|
fillsize -= dlen;
|
|
|
|
elem_cnt += cnt;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (peek_offset_cap) {
|
|
|
|
mh_sock.msg_iov = iov_vu + 1;
|
|
|
|
mh_sock.msg_iovlen = elem_cnt;
|
|
|
|
} else {
|
|
|
|
iov_vu[0].iov_base = tcp_buf_discard;
|
|
|
|
iov_vu[0].iov_len = already_sent;
|
|
|
|
|
|
|
|
mh_sock.msg_iov = iov_vu;
|
|
|
|
mh_sock.msg_iovlen = elem_cnt + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
do
|
|
|
|
ret = recvmsg(s, &mh_sock, MSG_PEEK);
|
|
|
|
while (ret < 0 && errno == EINTR);
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
vu_queue_rewind(vq, elem_cnt);
|
|
|
|
return -errno;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!peek_offset_cap)
|
|
|
|
ret -= already_sent;
|
|
|
|
|
|
|
|
/* adjust iov number and length of the last iov */
|
|
|
|
len = ret;
|
|
|
|
for (i = 0; len && i < elem_cnt; i++) {
|
|
|
|
struct iovec *iov = &elem[i].in_sg[0];
|
|
|
|
|
|
|
|
if (iov->iov_len > (size_t)len)
|
|
|
|
iov->iov_len = len;
|
|
|
|
|
|
|
|
len -= iov->iov_len;
|
|
|
|
}
|
|
|
|
/* adjust head count */
|
|
|
|
while (head_cnt > 0 && head[head_cnt - 1] > i)
|
|
|
|
head_cnt--;
|
|
|
|
/* mark end of array */
|
|
|
|
head[head_cnt] = i;
|
|
|
|
*iov_cnt = i;
|
|
|
|
|
|
|
|
/* release unused buffers */
|
|
|
|
vu_queue_rewind(vq, elem_cnt - i);
|
|
|
|
|
|
|
|
/* restore space for headers in iov */
|
|
|
|
for (i = 0; i < head_cnt; i++) {
|
|
|
|
struct iovec *iov = &elem[head[i]].in_sg[0];
|
|
|
|
|
|
|
|
iov->iov_base = (char *)iov->iov_base - hdrlen;
|
|
|
|
iov->iov_len += hdrlen;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* tcp_vu_prepare() - Prepare the frame header
|
2024-11-27 14:54:10 +11:00
|
|
|
* @c: Execution context
|
|
|
|
* @conn: Connection pointer
|
|
|
|
* @iov: Pointer to the array of IO vectors
|
|
|
|
* @iov_cnt: Number of entries in @iov
|
|
|
|
* @check: Checksum, if already known
|
|
|
|
* @no_tcp_csum: Do not set TCP checksum
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
*/
|
2024-11-27 14:54:07 +11:00
|
|
|
static void tcp_vu_prepare(const struct ctx *c, struct tcp_tap_conn *conn,
|
|
|
|
struct iovec *iov, size_t iov_cnt,
|
2024-11-27 14:54:10 +11:00
|
|
|
const uint16_t **check, bool no_tcp_csum)
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
{
|
|
|
|
const struct flowside *toside = TAPFLOW(conn);
|
2024-11-27 14:54:07 +11:00
|
|
|
bool v6 = !(inany_v4(&toside->eaddr) && inany_v4(&toside->oaddr));
|
|
|
|
size_t hdrlen = tcp_vu_hdrlen(v6);
|
|
|
|
struct iov_tail payload = IOV_TAIL(iov, iov_cnt, hdrlen);
|
|
|
|
char *base = iov[0].iov_base;
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
struct ipv6hdr *ip6h = NULL;
|
2024-11-27 14:54:09 +11:00
|
|
|
struct iphdr *ip4h = NULL;
|
2024-11-27 14:54:07 +11:00
|
|
|
struct tcphdr *th;
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
struct ethhdr *eh;
|
|
|
|
|
|
|
|
/* we guess the first iovec provided by the guest can embed
|
|
|
|
* all the headers needed by L2 frame
|
|
|
|
*/
|
2024-11-27 14:54:07 +11:00
|
|
|
ASSERT(iov[0].iov_len >= hdrlen);
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
|
|
|
|
eh = vu_eth(base);
|
|
|
|
|
|
|
|
memcpy(eh->h_dest, c->guest_mac, sizeof(eh->h_dest));
|
|
|
|
memcpy(eh->h_source, c->our_tap_mac, sizeof(eh->h_source));
|
|
|
|
|
|
|
|
/* initialize header */
|
|
|
|
|
2024-11-27 14:54:07 +11:00
|
|
|
if (!v6) {
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
eh->h_proto = htons(ETH_P_IP);
|
|
|
|
|
2024-11-27 14:54:09 +11:00
|
|
|
ip4h = vu_ip(base);
|
|
|
|
*ip4h = (struct iphdr)L2_BUF_IP4_INIT(IPPROTO_TCP);
|
2024-11-27 14:54:07 +11:00
|
|
|
th = vu_payloadv4(base);
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
} else {
|
|
|
|
eh->h_proto = htons(ETH_P_IPV6);
|
|
|
|
|
|
|
|
ip6h = vu_ip(base);
|
|
|
|
*ip6h = (struct ipv6hdr)L2_BUF_IP6_INIT(IPPROTO_TCP);
|
|
|
|
|
2024-11-27 14:54:07 +11:00
|
|
|
th = vu_payloadv6(base);
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
}
|
|
|
|
|
2024-11-27 14:54:07 +11:00
|
|
|
memset(th, 0, sizeof(*th));
|
|
|
|
th->doff = sizeof(*th) / 4;
|
|
|
|
th->ack = 1;
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
|
2024-11-27 14:54:09 +11:00
|
|
|
tcp_fill_headers(conn, NULL, ip4h, ip6h, th, &payload,
|
2024-11-27 14:54:10 +11:00
|
|
|
*check, conn->seq_to_tap, no_tcp_csum);
|
2024-11-27 14:54:09 +11:00
|
|
|
if (ip4h)
|
|
|
|
*check = &ip4h->check;
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* tcp_vu_data_from_sock() - Handle new data from socket, queue to vhost-user,
|
|
|
|
* in window
|
|
|
|
* @c: Execution context
|
|
|
|
* @conn: Connection pointer
|
|
|
|
*
|
|
|
|
* Return: Negative on connection reset, 0 otherwise
|
|
|
|
*/
|
|
|
|
int tcp_vu_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn)
|
|
|
|
{
|
|
|
|
uint32_t wnd_scaled = conn->wnd_from_tap << conn->ws_from_tap;
|
|
|
|
struct vu_dev *vdev = c->vdev;
|
|
|
|
struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE];
|
2024-11-28 13:08:41 +01:00
|
|
|
ssize_t len, previous_dlen;
|
2024-11-27 14:54:10 +11:00
|
|
|
size_t hdrlen, fillsize;
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
int v6 = CONN_V6(conn);
|
|
|
|
uint32_t already_sent;
|
|
|
|
const uint16_t *check;
|
|
|
|
int i, iov_cnt;
|
|
|
|
|
|
|
|
if (!vu_queue_enabled(vq) || !vu_queue_started(vq)) {
|
|
|
|
debug("Got packet, but RX virtqueue not usable yet");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
already_sent = conn->seq_to_tap - conn->seq_ack_from_tap;
|
|
|
|
|
|
|
|
if (SEQ_LT(already_sent, 0)) {
|
|
|
|
/* RFC 761, section 2.1. */
|
|
|
|
flow_trace(conn, "ACK sequence gap: ACK for %u, sent: %u",
|
|
|
|
conn->seq_ack_from_tap, conn->seq_to_tap);
|
|
|
|
conn->seq_to_tap = conn->seq_ack_from_tap;
|
|
|
|
already_sent = 0;
|
|
|
|
if (tcp_set_peek_offset(conn->sock, 0)) {
|
|
|
|
tcp_rst(c, conn);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!wnd_scaled || already_sent >= wnd_scaled) {
|
tcp: Mask EPOLLIN altogether if we're blocked waiting on an ACK from the guest
There are pretty much two cases of the (misnomer) STALLED: in one
case, we could send more data to the guest if it becomes available,
and in another case, we can't, because we filled the window.
If, in this second case, we keep EPOLLIN enabled, but never read from
the socket, we get short but CPU-annoying storms of EPOLLIN events,
upon which we reschedule the ACK timeout handler, never read from the
socket, go back to epoll_wait(), and so on:
timerfd_settime(76, 0, {it_interval={tv_sec=0, tv_nsec=0}, it_value={tv_sec=2, tv_nsec=0}}, NULL) = 0
epoll_wait(3, [{events=EPOLLIN, data={u32=10497, u64=38654716161}}], 8, 1000) = 1
timerfd_settime(76, 0, {it_interval={tv_sec=0, tv_nsec=0}, it_value={tv_sec=2, tv_nsec=0}}, NULL) = 0
epoll_wait(3, [{events=EPOLLIN, data={u32=10497, u64=38654716161}}], 8, 1000) = 1
timerfd_settime(76, 0, {it_interval={tv_sec=0, tv_nsec=0}, it_value={tv_sec=2, tv_nsec=0}}, NULL) = 0
epoll_wait(3, [{events=EPOLLIN, data={u32=10497, u64=38654716161}}], 8, 1000) = 1
also known as:
29.1517: Flow 2 (TCP connection): timer expires in 2.000s
29.1517: Flow 2 (TCP connection): timer expires in 2.000s
29.1517: Flow 2 (TCP connection): timer expires in 2.000s
which, for some reason, becomes very visible with muvm and aria2c
downloading from a server nearby in parallel chunks.
That's because EPOLLIN isn't cleared if we don't read from the socket,
and even with EPOLLET, epoll_wait() will repeatedly wake us up until
we actually read something.
In this case, we don't want to subscribe to EPOLLIN at all: all we're
waiting for is an ACK segment from the guest. Differentiate this case
with a new connection flag, ACK_FROM_TAP_BLOCKS, which doesn't just
indicate that we're waiting for an ACK from the guest
(ACK_FROM_TAP_DUE), but also that we're blocked waiting for it.
If this flag is set before we set STALLED, EPOLLIN will be masked
while we set EPOLLET because of STALLED. Whenever we clear STALLED,
we also clear this flag.
This is definitely not elegant, but it's a minimal fix.
We can probably simplify this at a later point by having a category
of connection flags directly corresponding to epoll flags, and
dropping STALLED altogether, or, perhaps, always using EPOLLET (but
we need a mechanism to re-check sockets for pending data if we can't
temporarily write to the guest).
I suspect that this might also be implied in
https://github.com/containers/podman/issues/23686, hence the Link:
tag. It doesn't necessarily mean I'm fixing it (I can't reproduce
that).
Link: https://github.com/containers/podman/issues/23686
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2025-01-16 20:47:00 +01:00
|
|
|
conn_flag(c, conn, ACK_FROM_TAP_BLOCKS);
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
conn_flag(c, conn, STALLED);
|
|
|
|
conn_flag(c, conn, ACK_FROM_TAP_DUE);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set up buffer descriptors we'll fill completely and partially. */
|
|
|
|
|
|
|
|
fillsize = wnd_scaled - already_sent;
|
|
|
|
|
|
|
|
/* collect the buffers from vhost-user and fill them with the
|
|
|
|
* data from the socket
|
|
|
|
*/
|
|
|
|
len = tcp_vu_sock_recv(c, conn, v6, already_sent, fillsize, &iov_cnt);
|
|
|
|
if (len < 0) {
|
|
|
|
if (len != -EAGAIN && len != -EWOULDBLOCK) {
|
|
|
|
tcp_rst(c, conn);
|
|
|
|
return len;
|
|
|
|
}
|
tcp: Set EPOLLET when when reading from a socket fails with EAGAIN
Before SO_PEEK_OFF support was introduced by commit e63d281871ef
("tcp: leverage support of SO_PEEK_OFF socket option when available"),
we would peek data from sockets using a "discard" buffer as first
iovec element, so that, unless we had no pending data at all, we would
always get a positive return code from recvmsg() (except for closing
connections or errors).
If we couldn't send more data to the guest, in the window, we would
set the STALLED flag (causing the epoll descriptor to switch to
edge-triggered mode), and return early from tcp_data_from_sock().
With SO_PEEK_OFF, we don't have a discard buffer, and if there's data
on the socket, but nothing beyond our current peeking offset, we'll
get EAGAIN instead of our current "discard" length. In that case, we
return even earlier, and we don't set EPOLLET on the socket as a
result.
As reported by Asahi Lina, this causes event loops where the kernel is
signalling socket readiness, because there's data we didn't dequeue
yet (waiting for the guest to acknowledge it), but we won't actually
peek anything new, and return early without setting EPOLLET.
This is the original report, mentioning the originally proposed fix:
--
When there is unacknowledged data in the inbound socket buffer, passt
leaves the socket in the epoll instance to accept new data from the
server. Since there is already data in the socket buffer, an epoll
without EPOLLET will repeatedly fire while no data is processed,
busy-looping the CPU:
epoll_pwait(3, [...], 8, 1000, NULL, 8) = 4
recvmsg(25, {msg_namelen=0}, MSG_PEEK) = -1 EAGAIN (Resource temporarily unavailable)
recvmsg(169, {msg_namelen=0}, MSG_PEEK) = -1 EAGAIN (Resource temporarily unavailable)
recvmsg(111, {msg_namelen=0}, MSG_PEEK) = -1 EAGAIN (Resource temporarily unavailable)
recvmsg(180, {msg_namelen=0}, MSG_PEEK) = -1 EAGAIN (Resource temporarily unavailable)
epoll_pwait(3, [...], 8, 1000, NULL, 8) = 4
recvmsg(25, {msg_namelen=0}, MSG_PEEK) = -1 EAGAIN (Resource temporarily unavailable)
recvmsg(169, {msg_namelen=0}, MSG_PEEK) = -1 EAGAIN (Resource temporarily unavailable)
recvmsg(111, {msg_namelen=0}, MSG_PEEK) = -1 EAGAIN (Resource temporarily unavailable)
recvmsg(180, {msg_namelen=0}, MSG_PEEK) = -1 EAGAIN (Resource temporarily unavailable)
Add in the missing EPOLLET flag for this case. This brings CPU
usage down from around ~80% when downloading over TCP, to ~5% (use
case: passt as network transport for muvm, downloading Steam games).
--
we can't set EPOLLET unconditionally though, at least right now,
because we don't monitor the guest tap for EPOLLOUT in case we fail
to write on that side because we filled up that buffer (and not the
window of a TCP connection).
Instead, rely on the observation that, once a connection is
established, we only get EAGAIN on recvmsg() if we are attempting to
peek data from a socket with a non-zero peeking offset: we only peek
when there's pending data on a socket, and in that case, if we peek
without offset, we'll always see some data.
And if we peek data with a non-zero offset and get EAGAIN, that means
that we're either waiting for more data to arrive on the socket (which
would cause further wake-ups, even with EPOLLET), or we're waiting for
the guest to acknowledge some of it, which would anyway cause a
wake-up.
In that case, it's safe to set STALLED and, in turn, EPOLLET on the
socket, which fixes the EPOLLIN event loop.
While we're establishing a connection from the socket side, though,
we'll call, once, tcp_{buf,vu}_data_from_sock() to see if we got
any data while we were waiting for SYN, ACK from the guest. See the
comment at the end of tcp_conn_from_sock_finish().
And if there's no data queued on the socket as we check, we'll also
get EAGAIN, even if our peeking offset is zero. For this reason, we
need to additionally check that 'already_sent' is not zero, meaning,
explicitly, that our peeking offset is not zero.
Reported-by: Asahi Lina <lina@asahilina.net>
Fixes: e63d281871ef ("tcp: leverage support of SO_PEEK_OFF socket option when available")
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2025-01-14 23:03:49 +01:00
|
|
|
|
|
|
|
if (already_sent) /* No new data and EAGAIN: set EPOLLET */
|
|
|
|
conn_flag(c, conn, STALLED);
|
|
|
|
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!len) {
|
|
|
|
if (already_sent) {
|
|
|
|
conn_flag(c, conn, STALLED);
|
|
|
|
} else if ((conn->events & (SOCK_FIN_RCVD | TAP_FIN_SENT)) ==
|
|
|
|
SOCK_FIN_RCVD) {
|
|
|
|
int ret = tcp_vu_send_flag(c, conn, FIN | ACK);
|
|
|
|
if (ret) {
|
|
|
|
tcp_rst(c, conn);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
conn_event(c, conn, TAP_FIN_SENT);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
tcp: Mask EPOLLIN altogether if we're blocked waiting on an ACK from the guest
There are pretty much two cases of the (misnomer) STALLED: in one
case, we could send more data to the guest if it becomes available,
and in another case, we can't, because we filled the window.
If, in this second case, we keep EPOLLIN enabled, but never read from
the socket, we get short but CPU-annoying storms of EPOLLIN events,
upon which we reschedule the ACK timeout handler, never read from the
socket, go back to epoll_wait(), and so on:
timerfd_settime(76, 0, {it_interval={tv_sec=0, tv_nsec=0}, it_value={tv_sec=2, tv_nsec=0}}, NULL) = 0
epoll_wait(3, [{events=EPOLLIN, data={u32=10497, u64=38654716161}}], 8, 1000) = 1
timerfd_settime(76, 0, {it_interval={tv_sec=0, tv_nsec=0}, it_value={tv_sec=2, tv_nsec=0}}, NULL) = 0
epoll_wait(3, [{events=EPOLLIN, data={u32=10497, u64=38654716161}}], 8, 1000) = 1
timerfd_settime(76, 0, {it_interval={tv_sec=0, tv_nsec=0}, it_value={tv_sec=2, tv_nsec=0}}, NULL) = 0
epoll_wait(3, [{events=EPOLLIN, data={u32=10497, u64=38654716161}}], 8, 1000) = 1
also known as:
29.1517: Flow 2 (TCP connection): timer expires in 2.000s
29.1517: Flow 2 (TCP connection): timer expires in 2.000s
29.1517: Flow 2 (TCP connection): timer expires in 2.000s
which, for some reason, becomes very visible with muvm and aria2c
downloading from a server nearby in parallel chunks.
That's because EPOLLIN isn't cleared if we don't read from the socket,
and even with EPOLLET, epoll_wait() will repeatedly wake us up until
we actually read something.
In this case, we don't want to subscribe to EPOLLIN at all: all we're
waiting for is an ACK segment from the guest. Differentiate this case
with a new connection flag, ACK_FROM_TAP_BLOCKS, which doesn't just
indicate that we're waiting for an ACK from the guest
(ACK_FROM_TAP_DUE), but also that we're blocked waiting for it.
If this flag is set before we set STALLED, EPOLLIN will be masked
while we set EPOLLET because of STALLED. Whenever we clear STALLED,
we also clear this flag.
This is definitely not elegant, but it's a minimal fix.
We can probably simplify this at a later point by having a category
of connection flags directly corresponding to epoll flags, and
dropping STALLED altogether, or, perhaps, always using EPOLLET (but
we need a mechanism to re-check sockets for pending data if we can't
temporarily write to the guest).
I suspect that this might also be implied in
https://github.com/containers/podman/issues/23686, hence the Link:
tag. It doesn't necessarily mean I'm fixing it (I can't reproduce
that).
Link: https://github.com/containers/podman/issues/23686
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2025-01-16 20:47:00 +01:00
|
|
|
conn_flag(c, conn, ~ACK_FROM_TAP_BLOCKS);
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
conn_flag(c, conn, ~STALLED);
|
|
|
|
|
|
|
|
/* Likely, some new data was acked too. */
|
|
|
|
tcp_update_seqack_wnd(c, conn, false, NULL);
|
|
|
|
|
|
|
|
/* initialize headers */
|
|
|
|
/* iov_vu is an array of buffers and the buffer size can be
|
|
|
|
* smaller than the frame size we want to use but with
|
|
|
|
* num_buffer we can merge several virtio iov buffers in one packet
|
|
|
|
* we need only to set the packet headers in the first iov and
|
|
|
|
* num_buffer to the number of iov entries
|
|
|
|
*/
|
|
|
|
|
|
|
|
hdrlen = tcp_vu_hdrlen(v6);
|
2024-11-28 13:08:41 +01:00
|
|
|
for (i = 0, previous_dlen = -1, check = NULL; i < head_cnt; i++) {
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
struct iovec *iov = &elem[head[i]].in_sg[0];
|
|
|
|
int buf_cnt = head[i + 1] - head[i];
|
2024-11-27 15:37:01 +01:00
|
|
|
ssize_t dlen = iov_size(iov, buf_cnt) - hdrlen;
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
|
|
|
|
vu_set_vnethdr(vdev, iov->iov_base, buf_cnt);
|
|
|
|
|
2024-11-28 13:08:41 +01:00
|
|
|
/* The IPv4 header checksum varies only with dlen */
|
|
|
|
if (previous_dlen != dlen)
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
check = NULL;
|
2024-11-28 13:08:41 +01:00
|
|
|
previous_dlen = dlen;
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
|
2024-11-27 14:54:10 +11:00
|
|
|
tcp_vu_prepare(c, conn, iov, buf_cnt, &check, !*c->pcap);
|
vhost-user: add vhost-user
add virtio and vhost-user functions to connect with QEMU.
$ ./passt --vhost-user
and
# qemu-system-x86_64 ... -m 4G \
-object memory-backend-memfd,id=memfd0,share=on,size=4G \
-numa node,memdev=memfd0 \
-chardev socket,id=chr0,path=/tmp/passt_1.socket \
-netdev vhost-user,id=netdev0,chardev=chr0 \
-device virtio-net,mac=9a:2b:2c:2d:2e:2f,netdev=netdev0 \
...
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
[sbrivio: as suggested by lvivier, include <netinet/if_ether.h>
before including <linux/if_ether.h> as C libraries such as musl
__UAPI_DEF_ETHHDR in <netinet/if_ether.h> if they already have
a definition of struct ethhdr]
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
2024-11-22 17:43:34 +01:00
|
|
|
|
|
|
|
if (*c->pcap) {
|
|
|
|
pcap_iov(iov, buf_cnt,
|
|
|
|
sizeof(struct virtio_net_hdr_mrg_rxbuf));
|
|
|
|
}
|
|
|
|
|
|
|
|
conn->seq_to_tap += dlen;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* send packets */
|
|
|
|
vu_flush(vdev, vq, elem, iov_cnt);
|
|
|
|
|
|
|
|
conn_flag(c, conn, ACK_FROM_TAP_DUE);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|