// SPDX-License-Identifier: GPL-2.0-or-later /* udp_vu.c - UDP L2 vhost-user management functions * * Copyright Red Hat * Author: Laurent Vivier */ #include #include #include #include #include #include #include #include #include #include #include #include "checksum.h" #include "util.h" #include "ip.h" #include "siphash.h" #include "inany.h" #include "passt.h" #include "pcap.h" #include "log.h" #include "vhost_user.h" #include "udp_internal.h" #include "flow.h" #include "flow_table.h" #include "udp_flow.h" #include "udp_vu.h" #include "vu_common.h" static struct iovec iov_vu [VIRTQUEUE_MAX_SIZE]; static struct vu_virtq_element elem [VIRTQUEUE_MAX_SIZE]; /** * udp_vu_hdrlen() - Sum size of all headers, from UDP to virtio-net * @v6: Set for IPv6 packet * * Return: total size of virtio-net, Ethernet, IP, and UDP headers */ static size_t udp_vu_hdrlen(bool v6) { size_t hdrlen; hdrlen = VNET_HLEN + sizeof(struct ethhdr) + sizeof(struct udphdr); if (v6) hdrlen += sizeof(struct ipv6hdr); else hdrlen += sizeof(struct iphdr); return hdrlen; } /** * udp_vu_sock_recv() - Receive datagrams from socket into vhost-user buffers * @data: IO vector tail for the frame (modified on output) * @s: Socket to receive from * @v6: Set for IPv6 connections * * Return: size of received data, -1 on error */ static ssize_t udp_vu_sock_recv(struct iov_tail *data, int s, bool v6) { struct iovec msg_iov[data->cnt]; struct msghdr msg = { 0 }; struct iov_tail payload; size_t hdrlen; ssize_t dlen; /* compute L2 header length */ hdrlen = udp_vu_hdrlen(v6); /* reserve space for the headers */ ASSERT(iov_tail_size(data) >= MAX(hdrlen, ETH_ZLEN + VNET_HLEN)); payload = *data; iov_drop_header(&payload, hdrlen); msg.msg_iov = msg_iov; msg.msg_iovlen = iov_tail_clone(msg.msg_iov, payload.cnt, &payload); /* read data from the socket */ dlen = recvmsg(s, &msg, 0); if (dlen < 0) return -1; iov_tail_truncate(data, MAX(dlen + hdrlen, ETH_ZLEN + VNET_HLEN)); iov_tail_zero_end(data, dlen + hdrlen); iov_tail_truncate(data, dlen + hdrlen); return dlen; } /** * udp_vu_prepare() - Prepare the packet header * @c: Execution context * @data: IO vector tail for the frame * @toside: Address information for one side of the flow * * Return: Layer-4 length */ static size_t udp_vu_prepare(const struct ctx *c, const struct iov_tail *data, const struct flowside *toside) { struct iov_tail current = *data; struct ethhdr *eh, eh_storage; struct udphdr *uh, uh_storage; size_t l4len; /* ethernet header */ eh = IOV_REMOVE_HEADER(¤t, eh_storage); 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 */ if (inany_v4(&toside->eaddr) && inany_v4(&toside->oaddr)) { struct iphdr *iph, iph_storage; eh->h_proto = htons(ETH_P_IP); iph = IOV_REMOVE_HEADER(¤t, iph_storage); *iph = (struct iphdr)L2_BUF_IP4_INIT(IPPROTO_UDP); uh = IOV_REMOVE_HEADER(¤t, uh_storage); l4len = udp_update_hdr4(iph, uh, ¤t, toside, true); current = *data; IOV_PUT_HEADER(¤t, eh); IOV_PUT_HEADER(¤t, iph); IOV_PUT_HEADER(¤t, uh); } else { struct ipv6hdr *ip6h, ip6h_storage; eh->h_proto = htons(ETH_P_IPV6); ip6h = IOV_REMOVE_HEADER(¤t, ip6h_storage); *ip6h = (struct ipv6hdr)L2_BUF_IP6_INIT(IPPROTO_UDP); uh = IOV_REMOVE_HEADER(¤t, uh_storage); l4len = udp_update_hdr6(ip6h, uh, ¤t, toside, true); current = *data; IOV_PUT_HEADER(¤t, eh); IOV_PUT_HEADER(¤t, ip6h); IOV_PUT_HEADER(¤t, uh); } return l4len; } /** * udp_vu_csum() - Calculate and set checksum for a UDP packet * @toside: Address information for one side of the flow * @data: IO vector tail for the frame */ static void udp_vu_csum(const struct flowside *toside, const struct iov_tail *data) { const struct in_addr *src4 = inany_v4(&toside->oaddr); const struct in_addr *dst4 = inany_v4(&toside->eaddr); struct iov_tail payload = *data; struct udphdr *uh, uh_storage; bool ipv4 = src4 && dst4; int hdrlen = sizeof(struct ethhdr) + (ipv4 ? sizeof(struct iphdr) : sizeof(struct ipv6hdr)); iov_drop_header(&payload, hdrlen); uh = IOV_REMOVE_HEADER(&payload, uh_storage); if (ipv4) csum_udp4(uh, *src4, *dst4, &payload); else csum_udp6(uh, &toside->oaddr.a6, &toside->eaddr.a6, &payload); } /** * udp_vu_sock_to_tap() - Forward datagrams from socket to tap * @c: Execution context * @s: Socket to read data from * @n: Maximum number of datagrams to forward * @tosidx: Flow & side to forward data from @s to */ void udp_vu_sock_to_tap(const struct ctx *c, int s, int n, flow_sidx_t tosidx) { const struct flowside *toside = flowside_at_sidx(tosidx); bool v6 = !(inany_v4(&toside->eaddr) && inany_v4(&toside->oaddr)); struct vu_dev *vdev = c->vdev; struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE]; struct iov_tail data; int i; ASSERT(!c->no_udp); if (!vu_queue_enabled(vq) || !vu_queue_started(vq)) { struct msghdr msg = { 0 }; debug("Got UDP packet, but RX virtqueue not usable yet"); for (i = 0; i < n; i++) { if (recvmsg(s, &msg, MSG_DONTWAIT) < 0) debug_perror("Failed to discard datagram"); } return; } for (i = 0; i < n; i++) { int elem_cnt, elem_used; ssize_t dlen; vu_init_elem(elem, iov_vu, ARRAY_SIZE(elem), 1); elem_cnt = vu_collect(vdev, vq, elem, ARRAY_SIZE(elem), IP_MAX_MTU + ETH_HLEN + VNET_HLEN, NULL); if (elem_cnt == 0) break; data = IOV_TAIL(iov_vu, elem_cnt, 0); dlen = udp_vu_sock_recv(&data, s, v6); if (dlen < 0) { vu_queue_rewind(vq, elem_cnt); continue; } elem_used = data.cnt; /* release unused buffers */ vu_queue_rewind(vq, elem_cnt - elem_used); if (data.cnt > 0) { vu_set_vnethdr(vdev, &data, elem_used); udp_vu_prepare(c, &data, toside); if (*c->pcap) { udp_vu_csum(toside, &data); pcap_iov(data.iov, data.cnt, 0); } vu_flush(vdev, vq, elem, elem_used); } } }