mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-29 11:54:38 +01:00
vtnet: improve checksum offloading
When transmitting a packet over the vtnet interface, map the csum flags CSUM_DATA_VALID | CSUM_PSEUDO_HDR to the virtio flag VIRTIO_NET_HDR_F_DATA_VALID. When receiving a packet over the virtio network channel, translate the virtio flag VIRTIO_NET_HDR_F_NEEDS_CSUM not to CSUM_DATA_VALID | CSUM_PSEUDO_HDR, but to CSUM_TCP, CSUM_TCP_IPV6, CSUM_UDP, or CSUM_UDP_IPV6. The second change fixes a series of issue related to checksum offloading for if_vtnet. While there, improve the stats counters to allow a detailed view on what is going on in relation to checksum offloading. PR: 165059 Reviewed by: tuexen, manpages MFC after: 3 days Differential Revision: https://reviews.freebsd.org/D51686
This commit is contained in:
1 parent
f217bc7651
commit
3008f30d2c
3 files changed
+143
-129
No files matched your search
+19
-9
@@ -22,7 +22,7 @@
|
||||
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
.\" SUCH DAMAGE.
|
||||
.\"
|
||||
.Dd September 3, 2025
|
||||
.Dd September 8, 2025
|
||||
.Dt VTNET 4
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -153,7 +153,14 @@ The number of times the receive interrupt handler was rescheduled.
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rxq Ns Ar Y Ns Va .host_lro
|
||||
The number of times TCP large receive offload was performed.
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rxq Ns Ar Y Ns Va .csum_failed
|
||||
Currently not used.
|
||||
The number of times a packet with a request for receive or transmit checksum
|
||||
offloading was received and this request failed.
|
||||
The different reasons for the failure are counted by
|
||||
.Va dev.vtnet. Ns Ar X Ns Va .rx_csum_inaccessible_ipproto ,
|
||||
.Va dev.vtnet. Ns Ar X Ns Va .rx_csum_bad_ipproto ,
|
||||
.Va dev.vtnet. Ns Ar X Ns Va .rx_csum_bad_ethtype ,
|
||||
and
|
||||
.Va dev.vtnet. Ns Ar X Ns Va .rx_csum_bad_offset .
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rxq Ns Ar Y Ns Va .csum
|
||||
The number of times receive checksum offloading for UDP or TCP was performed.
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rxq Ns Ar Y Ns Va .ierrors
|
||||
@@ -213,18 +220,21 @@ over all receive queues of the interface.
|
||||
The sum of
|
||||
.Va dev.vtnet. Ns Ar X Ns Va .rxq Ns Ar Y Ns Va .csum_failed
|
||||
over all receive queues of the interface.
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rx_csum_bad_proto
|
||||
Currently unused.
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rx_csum_inaccessible_ipproto
|
||||
The number of times a packet with a request for receive or transmit checksum
|
||||
offloading was received where the IP protocol was not accessible.
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rx_csum_bad_offset
|
||||
Currently unused.
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rx_csum_bad_ipproto
|
||||
Currently unused.
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rx_csum_bad_ethtype
|
||||
The number of times fixing the checksum required by
|
||||
.Va hw.vtnet.fixup_needs_csum
|
||||
or
|
||||
.Va hw.vtnet. Ns Ar X Ns Va .fixup_needs_csum
|
||||
was attempted for a packet with an EtherType other than IPv4 or IPv6.
|
||||
was attempted for a packet where the csum is not located in the first mbuf.
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rx_csum_bad_ipproto
|
||||
The number of times a packet with a request for receive or transmit checksum
|
||||
offloading was received where the IP protocol was neither TCP nor UDP.
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rx_csum_bad_ethtype
|
||||
The number of times a packet with a request for receive or transmit checksum
|
||||
offloading was received where the EtherType was neither IPv4 nor IPv6.
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rx_mergeable_failed
|
||||
The number of times receiving a mergable buffer failed.
|
||||
.It Va dev.vtnet. Ns Ar X Ns Va .rx_enq_replacement_failed
|
||||
|
||||
+123
-119
@@ -134,9 +134,9 @@ static int vtnet_rxq_replace_buf(struct vtnet_rxq *, struct mbuf *, int);
|
||||
static int vtnet_rxq_enqueue_buf(struct vtnet_rxq *, struct mbuf *);
|
||||
static int vtnet_rxq_new_buf(struct vtnet_rxq *);
|
||||
static int vtnet_rxq_csum_needs_csum(struct vtnet_rxq *, struct mbuf *,
|
||||
uint16_t, int, struct virtio_net_hdr *);
|
||||
static int vtnet_rxq_csum_data_valid(struct vtnet_rxq *, struct mbuf *,
|
||||
uint16_t, int, struct virtio_net_hdr *);
|
||||
bool, int, struct virtio_net_hdr *);
|
||||
static void vtnet_rxq_csum_data_valid(struct vtnet_rxq *, struct mbuf *,
|
||||
int);
|
||||
static int vtnet_rxq_csum(struct vtnet_rxq *, struct mbuf *,
|
||||
struct virtio_net_hdr *);
|
||||
static void vtnet_rxq_discard_merged_bufs(struct vtnet_rxq *, int);
|
||||
@@ -1762,137 +1762,82 @@ vtnet_rxq_new_buf(struct vtnet_rxq *rxq)
|
||||
}
|
||||
|
||||
static int
|
||||
vtnet_rxq_csum_needs_csum(struct vtnet_rxq *rxq, struct mbuf *m, uint16_t etype,
|
||||
int hoff, struct virtio_net_hdr *hdr)
|
||||
vtnet_rxq_csum_needs_csum(struct vtnet_rxq *rxq, struct mbuf *m, bool isipv6,
|
||||
int protocol, struct virtio_net_hdr *hdr)
|
||||
{
|
||||
struct vtnet_softc *sc;
|
||||
int error;
|
||||
|
||||
sc = rxq->vtnrx_sc;
|
||||
|
||||
/*
|
||||
* NEEDS_CSUM corresponds to Linux's CHECKSUM_PARTIAL, but FreeBSD does
|
||||
* not have an analogous CSUM flag. The checksum has been validated,
|
||||
* but is incomplete (TCP/UDP pseudo header).
|
||||
*
|
||||
* The packet is likely from another VM on the same host that itself
|
||||
* performed checksum offloading so Tx/Rx is basically a memcpy and
|
||||
* the checksum has little value.
|
||||
*
|
||||
* Default to receiving the packet as-is for performance reasons, but
|
||||
* this can cause issues if the packet is to be forwarded because it
|
||||
* does not contain a valid checksum. This patch may be helpful:
|
||||
* https://reviews.freebsd.org/D6611. In the meantime, have the driver
|
||||
* compute the checksum if requested.
|
||||
*
|
||||
* BMV: Need to add an CSUM_PARTIAL flag?
|
||||
* The packet is likely from another VM on the same host or from the
|
||||
* host that itself performed checksum offloading so Tx/Rx is basically
|
||||
* a memcpy and the checksum has little value so far.
|
||||
*/
|
||||
|
||||
KASSERT(protocol == IPPROTO_TCP || protocol == IPPROTO_UDP,
|
||||
("%s: unsupported IP protocol %d", __func__, protocol));
|
||||
|
||||
/*
|
||||
* If the user don't want us to fix it up here by computing the
|
||||
* checksum, just forward the order to compute the checksum by setting
|
||||
* the corresponding mbuf flag (e.g., CSUM_TCP).
|
||||
*/
|
||||
sc = rxq->vtnrx_sc;
|
||||
if ((sc->vtnet_flags & VTNET_FLAG_FIXUP_NEEDS_CSUM) == 0) {
|
||||
error = vtnet_rxq_csum_data_valid(rxq, m, etype, hoff, hdr);
|
||||
return (error);
|
||||
switch (protocol) {
|
||||
case IPPROTO_TCP:
|
||||
m->m_pkthdr.csum_flags |=
|
||||
(isipv6 ? CSUM_TCP_IPV6 : CSUM_TCP);
|
||||
break;
|
||||
case IPPROTO_UDP:
|
||||
m->m_pkthdr.csum_flags |=
|
||||
(isipv6 ? CSUM_UDP_IPV6 : CSUM_UDP);
|
||||
break;
|
||||
}
|
||||
m->m_pkthdr.csum_data = hdr->csum_offset;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the checksum in the driver so the packet will contain a
|
||||
* valid checksum. The checksum is at csum_offset from csum_start.
|
||||
*/
|
||||
switch (etype) {
|
||||
#if defined(INET) || defined(INET6)
|
||||
case ETHERTYPE_IP:
|
||||
case ETHERTYPE_IPV6: {
|
||||
int csum_off, csum_end;
|
||||
uint16_t csum;
|
||||
int csum_off, csum_end;
|
||||
uint16_t csum;
|
||||
|
||||
csum_off = hdr->csum_start + hdr->csum_offset;
|
||||
csum_end = csum_off + sizeof(uint16_t);
|
||||
csum_off = hdr->csum_start + hdr->csum_offset;
|
||||
csum_end = csum_off + sizeof(uint16_t);
|
||||
|
||||
/* Assume checksum will be in the first mbuf. */
|
||||
if (m->m_len < csum_end || m->m_pkthdr.len < csum_end)
|
||||
return (1);
|
||||
|
||||
/*
|
||||
* Like in_delayed_cksum()/in6_delayed_cksum(), compute the
|
||||
* checksum and write it at the specified offset. We could
|
||||
* try to verify the packet: csum_start should probably
|
||||
* correspond to the start of the TCP/UDP header.
|
||||
*
|
||||
* BMV: Need to properly handle UDP with zero checksum. Is
|
||||
* the IPv4 header checksum implicitly validated?
|
||||
*/
|
||||
csum = in_cksum_skip(m, m->m_pkthdr.len, hdr->csum_start);
|
||||
*(uint16_t *)(mtodo(m, csum_off)) = csum;
|
||||
m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
|
||||
m->m_pkthdr.csum_data = 0xFFFF;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
sc->vtnet_stats.rx_csum_bad_ethtype++;
|
||||
/* Assume checksum will be in the first mbuf. */
|
||||
if (m->m_len < csum_end || m->m_pkthdr.len < csum_end) {
|
||||
sc->vtnet_stats.rx_csum_bad_offset++;
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Like in_delayed_cksum()/in6_delayed_cksum(), compute the
|
||||
* checksum and write it at the specified offset. We could
|
||||
* try to verify the packet: csum_start should probably
|
||||
* correspond to the start of the TCP/UDP header.
|
||||
*
|
||||
* BMV: Need to properly handle UDP with zero checksum. Is
|
||||
* the IPv4 header checksum implicitly validated?
|
||||
*/
|
||||
csum = in_cksum_skip(m, m->m_pkthdr.len, hdr->csum_start);
|
||||
*(uint16_t *)(mtodo(m, csum_off)) = csum;
|
||||
m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
|
||||
m->m_pkthdr.csum_data = 0xFFFF;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
vtnet_rxq_csum_data_valid(struct vtnet_rxq *rxq, struct mbuf *m,
|
||||
uint16_t etype, int hoff, struct virtio_net_hdr *hdr __unused)
|
||||
static void
|
||||
vtnet_rxq_csum_data_valid(struct vtnet_rxq *rxq, struct mbuf *m, int protocol)
|
||||
{
|
||||
#if 0
|
||||
struct vtnet_softc *sc;
|
||||
#endif
|
||||
int protocol;
|
||||
KASSERT(protocol == IPPROTO_TCP || protocol == IPPROTO_UDP,
|
||||
("%s: unsupported IP protocol %d", __func__, protocol));
|
||||
|
||||
#if 0
|
||||
sc = rxq->vtnrx_sc;
|
||||
#endif
|
||||
|
||||
switch (etype) {
|
||||
#if defined(INET)
|
||||
case ETHERTYPE_IP:
|
||||
if (__predict_false(m->m_len < hoff + sizeof(struct ip)))
|
||||
protocol = IPPROTO_DONE;
|
||||
else {
|
||||
struct ip *ip = (struct ip *)(m->m_data + hoff);
|
||||
protocol = ip->ip_p;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#if defined(INET6)
|
||||
case ETHERTYPE_IPV6:
|
||||
if (__predict_false(m->m_len < hoff + sizeof(struct ip6_hdr))
|
||||
|| ip6_lasthdr(m, hoff, IPPROTO_IPV6, &protocol) < 0)
|
||||
protocol = IPPROTO_DONE;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
protocol = IPPROTO_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (protocol) {
|
||||
case IPPROTO_TCP:
|
||||
case IPPROTO_UDP:
|
||||
m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
|
||||
m->m_pkthdr.csum_data = 0xFFFF;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* FreeBSD does not support checksum offloading of this
|
||||
* protocol. Let the stack re-verify the checksum later
|
||||
* if the protocol is supported.
|
||||
*/
|
||||
#if 0
|
||||
if_printf(sc->vtnet_ifp,
|
||||
"%s: checksum offload of unsupported protocol "
|
||||
"etype=%#x protocol=%d csum_start=%d csum_offset=%d\n",
|
||||
__func__, etype, protocol, hdr->csum_start,
|
||||
hdr->csum_offset);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
return (0);
|
||||
m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
|
||||
m->m_pkthdr.csum_data = 0xFFFF;
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -1900,8 +1845,14 @@ vtnet_rxq_csum(struct vtnet_rxq *rxq, struct mbuf *m,
|
||||
struct virtio_net_hdr *hdr)
|
||||
{
|
||||
const struct ether_header *eh;
|
||||
int hoff;
|
||||
struct vtnet_softc *sc;
|
||||
int hoff, protocol;
|
||||
uint16_t etype;
|
||||
bool isipv6;
|
||||
|
||||
KASSERT(hdr->flags &
|
||||
(VIRTIO_NET_HDR_F_NEEDS_CSUM | VIRTIO_NET_HDR_F_DATA_VALID),
|
||||
("%s: missing checksum offloading flag %x", __func__, hdr->flags));
|
||||
|
||||
eh = mtod(m, const struct ether_header *);
|
||||
etype = ntohs(eh->ether_type);
|
||||
@@ -1914,10 +1865,58 @@ vtnet_rxq_csum(struct vtnet_rxq *rxq, struct mbuf *m,
|
||||
} else
|
||||
hoff = sizeof(struct ether_header);
|
||||
|
||||
sc = rxq->vtnrx_sc;
|
||||
|
||||
/* Check whether ethernet type is IP or IPv6, and get protocol. */
|
||||
switch (etype) {
|
||||
#if defined(INET)
|
||||
case ETHERTYPE_IP:
|
||||
if (__predict_false(m->m_len < hoff + sizeof(struct ip))) {
|
||||
sc->vtnet_stats.rx_csum_inaccessible_ipproto++;
|
||||
return (1);
|
||||
} else {
|
||||
struct ip *ip = (struct ip *)(m->m_data + hoff);
|
||||
protocol = ip->ip_p;
|
||||
}
|
||||
isipv6 = false;
|
||||
break;
|
||||
#endif
|
||||
#if defined(INET6)
|
||||
case ETHERTYPE_IPV6:
|
||||
if (__predict_false(m->m_len < hoff + sizeof(struct ip6_hdr))
|
||||
|| ip6_lasthdr(m, hoff, IPPROTO_IPV6, &protocol) < 0) {
|
||||
sc->vtnet_stats.rx_csum_inaccessible_ipproto++;
|
||||
return (1);
|
||||
}
|
||||
isipv6 = true;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
sc->vtnet_stats.rx_csum_bad_ethtype++;
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Check whether protocol is TCP or UDP. */
|
||||
switch (protocol) {
|
||||
case IPPROTO_TCP:
|
||||
case IPPROTO_UDP:
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* FreeBSD does not support checksum offloading of this
|
||||
* protocol here.
|
||||
*/
|
||||
sc->vtnet_stats.rx_csum_bad_ipproto++;
|
||||
return (1);
|
||||
}
|
||||
|
||||
if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)
|
||||
return (vtnet_rxq_csum_needs_csum(rxq, m, etype, hoff, hdr));
|
||||
return (vtnet_rxq_csum_needs_csum(rxq, m, isipv6, protocol,
|
||||
hdr));
|
||||
else /* VIRTIO_NET_HDR_F_DATA_VALID */
|
||||
return (vtnet_rxq_csum_data_valid(rxq, m, etype, hoff, hdr));
|
||||
vtnet_rxq_csum_data_valid(rxq, m, protocol);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -2498,6 +2497,10 @@ vtnet_txq_offload(struct vtnet_txq *txq, struct mbuf *m,
|
||||
hdr->csum_start = vtnet_gtoh16(sc, csum_start);
|
||||
hdr->csum_offset = vtnet_gtoh16(sc, m->m_pkthdr.csum_data);
|
||||
txq->vtntx_stats.vtxs_csum++;
|
||||
} else if ((flags & (CSUM_DATA_VALID | CSUM_PSEUDO_HDR)) &&
|
||||
(proto == IPPROTO_TCP || proto == IPPROTO_UDP) &&
|
||||
(m->m_pkthdr.csum_data == 0xFFFF)) {
|
||||
hdr->flags |= VIRTIO_NET_HDR_F_DATA_VALID;
|
||||
}
|
||||
|
||||
if (flags & (CSUM_IP_TSO | CSUM_IP6_TSO)) {
|
||||
@@ -2611,7 +2614,8 @@ vtnet_txq_encap(struct vtnet_txq *txq, struct mbuf **m_head, int flags)
|
||||
m->m_flags &= ~M_VLANTAG;
|
||||
}
|
||||
|
||||
if (m->m_pkthdr.csum_flags & VTNET_CSUM_ALL_OFFLOAD) {
|
||||
if (m->m_pkthdr.csum_flags &
|
||||
(VTNET_CSUM_ALL_OFFLOAD | CSUM_DATA_VALID)) {
|
||||
m = vtnet_txq_offload(txq, m, hdr);
|
||||
if ((*m_head = m) == NULL) {
|
||||
error = ENOBUFS;
|
||||
@@ -4321,9 +4325,9 @@ vtnet_setup_stat_sysctl(struct sysctl_ctx_list *ctx,
|
||||
SYSCTL_ADD_UQUAD(ctx, child, OID_AUTO, "rx_csum_bad_offset",
|
||||
CTLFLAG_RD | CTLFLAG_STATS, &stats->rx_csum_bad_offset,
|
||||
"Received checksum offloaded buffer with incorrect offset");
|
||||
SYSCTL_ADD_UQUAD(ctx, child, OID_AUTO, "rx_csum_bad_proto",
|
||||
CTLFLAG_RD | CTLFLAG_STATS, &stats->rx_csum_bad_proto,
|
||||
"Received checksum offloaded buffer with incorrect protocol");
|
||||
SYSCTL_ADD_UQUAD(ctx, child, OID_AUTO, "rx_csum_inaccessible_ipproto",
|
||||
CTLFLAG_RD | CTLFLAG_STATS, &stats->rx_csum_inaccessible_ipproto,
|
||||
"Received checksum offloaded buffer with inaccessible IP protocol");
|
||||
SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "rx_csum_failed",
|
||||
CTLTYPE_U64 | CTLFLAG_RD | CTLFLAG_STATS,
|
||||
sc, 0, vtnet_sysctl_rx_csum_failed, "QU",
|
||||
|
||||
@@ -46,7 +46,7 @@ struct vtnet_statistics {
|
||||
uint64_t rx_csum_bad_ethtype;
|
||||
uint64_t rx_csum_bad_ipproto;
|
||||
uint64_t rx_csum_bad_offset;
|
||||
uint64_t rx_csum_bad_proto;
|
||||
uint64_t rx_csum_inaccessible_ipproto;
|
||||
uint64_t tx_csum_unknown_ethtype;
|
||||
uint64_t tx_csum_proto_mismatch;
|
||||
uint64_t tx_tso_not_tcp;
|
||||
|
||||
Reference in new issue
Block a user