mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-11-07 04:36:52 -10:00
Revert "vtnet: move offload functions to virtio_net.h to share them"
This reverts commit 44cddaa99d.
It breaks the LINT-NOIP config.
This commit is contained in:
1 parent
a793cabb62
commit
8bea280f43
4 files changed
+466
-277
No files matched your search
@@ -1464,17 +1464,8 @@ ptnet_drain_transmit_queue(struct ptnet_queue *pq, unsigned int budget,
|
||||
* two 8-bytes-wide writes. */
|
||||
memset(nmbuf, 0, PTNET_HDR_SIZE);
|
||||
if (mhead->m_pkthdr.csum_flags & PTNET_ALL_OFFLOAD) {
|
||||
/*
|
||||
* Translate the CSUM_* flags in the mbuf to the
|
||||
* corresponding flags in the VirtIO header.
|
||||
*
|
||||
* ptnet does not negotiate ECN and orders the
|
||||
* bytes in the VirtIO header as if the VirtIO
|
||||
* modern mode is not used. So, pass false for
|
||||
* both.
|
||||
*/
|
||||
virtio_net_tx_offload(ifp, &mhead, vh, false,
|
||||
false);
|
||||
mhead = virtio_net_tx_offload(ifp, mhead, false,
|
||||
vh);
|
||||
if (unlikely(!mhead)) {
|
||||
/* Packet dropped because errors
|
||||
* occurred while preparing the vnet
|
||||
@@ -1851,14 +1842,7 @@ host_sync:
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Translate the VirtIO header flags to the corresponding
|
||||
* CSUM_* flags in the mbuf.
|
||||
*/
|
||||
if (unlikely(have_vnet_hdr &&
|
||||
((vh->flags & (VIRTIO_NET_HDR_F_NEEDS_CSUM |
|
||||
VIRTIO_NET_HDR_F_DATA_VALID)) != 0) &&
|
||||
(virtio_net_rx_csum(mhead, vh) != 0))) {
|
||||
if (unlikely(have_vnet_hdr && virtio_net_rx_csum(mhead, vh))) {
|
||||
m_freem(mhead);
|
||||
nm_prlim(1, "Csum offload error: dropping");
|
||||
pq->stats.iqdrops ++;
|
||||
|
||||
@@ -145,6 +145,14 @@ static int vtnet_rxq_replace_lro_nomrg_buf(struct vtnet_rxq *,
|
||||
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 *);
|
||||
#if defined(INET) || defined(INET6)
|
||||
static void vtnet_rxq_csum_needs_csum(struct vtnet_rxq *, struct mbuf *,
|
||||
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 *);
|
||||
#endif
|
||||
static void vtnet_rxq_discard_merged_bufs(struct vtnet_rxq *, int);
|
||||
static void vtnet_rxq_discard_buf(struct vtnet_rxq *, struct mbuf *);
|
||||
static int vtnet_rxq_merged_eof(struct vtnet_rxq *, struct mbuf *, int);
|
||||
@@ -159,6 +167,13 @@ static int vtnet_txq_intr_threshold(struct vtnet_txq *);
|
||||
static int vtnet_txq_below_threshold(struct vtnet_txq *);
|
||||
static int vtnet_txq_notify(struct vtnet_txq *);
|
||||
static void vtnet_txq_free_mbufs(struct vtnet_txq *);
|
||||
static int vtnet_txq_offload_ctx(struct vtnet_txq *, struct mbuf *,
|
||||
int *, int *, int *);
|
||||
static int vtnet_txq_offload_tso(struct vtnet_txq *, struct mbuf *, int,
|
||||
int, struct virtio_net_hdr *);
|
||||
static struct mbuf *
|
||||
vtnet_txq_offload(struct vtnet_txq *, struct mbuf *,
|
||||
struct virtio_net_hdr *);
|
||||
static int vtnet_txq_enqueue_buf(struct vtnet_txq *, struct mbuf **,
|
||||
struct vtnet_tx_header *);
|
||||
static int vtnet_txq_encap(struct vtnet_txq *, struct mbuf **, int);
|
||||
@@ -1949,6 +1964,124 @@ vtnet_rxq_new_buf(struct vtnet_rxq *rxq)
|
||||
return (error);
|
||||
}
|
||||
|
||||
#if defined(INET) || defined(INET6)
|
||||
static void
|
||||
vtnet_rxq_csum_needs_csum(struct vtnet_rxq *rxq, struct mbuf *m, bool isipv6,
|
||||
int protocol, struct virtio_net_hdr *hdr)
|
||||
{
|
||||
/*
|
||||
* 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));
|
||||
|
||||
/*
|
||||
* Just forward the order to compute the checksum by setting
|
||||
* the corresponding mbuf flag (e.g., CSUM_TCP).
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
static void
|
||||
vtnet_rxq_csum_data_valid(struct vtnet_rxq *rxq, struct mbuf *m, int protocol)
|
||||
{
|
||||
KASSERT(protocol == IPPROTO_TCP || protocol == IPPROTO_UDP,
|
||||
("%s: unsupported IP protocol %d", __func__, protocol));
|
||||
|
||||
m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
|
||||
m->m_pkthdr.csum_data = 0xFFFF;
|
||||
}
|
||||
|
||||
static int
|
||||
vtnet_rxq_csum(struct vtnet_rxq *rxq, struct mbuf *m,
|
||||
struct virtio_net_hdr *hdr)
|
||||
{
|
||||
const struct ether_header *eh;
|
||||
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);
|
||||
if (etype == ETHERTYPE_VLAN) {
|
||||
/* TODO BMV: Handle QinQ. */
|
||||
const struct ether_vlan_header *evh =
|
||||
mtod(m, const struct ether_vlan_header *);
|
||||
etype = ntohs(evh->evl_proto);
|
||||
hoff = sizeof(struct ether_vlan_header);
|
||||
} 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)
|
||||
vtnet_rxq_csum_needs_csum(rxq, m, isipv6, protocol, hdr);
|
||||
else /* VIRTIO_NET_HDR_F_DATA_VALID */
|
||||
vtnet_rxq_csum_data_valid(rxq, m, protocol);
|
||||
|
||||
return (0);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
vtnet_rxq_discard_merged_bufs(struct vtnet_rxq *rxq, int nbufs)
|
||||
{
|
||||
@@ -2087,29 +2220,10 @@ vtnet_rxq_input(struct vtnet_rxq *rxq, struct mbuf *m,
|
||||
if (hdr->flags &
|
||||
(VIRTIO_NET_HDR_F_NEEDS_CSUM | VIRTIO_NET_HDR_F_DATA_VALID)) {
|
||||
#if defined(INET) || defined(INET6)
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Translate the VirtIO header flags to the corresponding
|
||||
* CSUM_* flags in the mbuf.
|
||||
*/
|
||||
ret = virtio_net_rx_csum(m, hdr);
|
||||
if (ret == 0)
|
||||
if (vtnet_rxq_csum(rxq, m, hdr) == 0)
|
||||
rxq->vtnrx_stats.vrxs_csum++;
|
||||
else {
|
||||
switch (ret) {
|
||||
case VIRTIO_NET_RX_CSUM_INACCESSIBLE_IPPROTO:
|
||||
sc->vtnet_stats.rx_csum_inaccessible_ipproto++;
|
||||
break;
|
||||
case VIRTIO_NET_RX_CSUM_BAD_ETHTYPE:
|
||||
sc->vtnet_stats.rx_csum_bad_ethtype++;
|
||||
break;
|
||||
case VIRTIO_NET_RX_CSUM_BAD_IPPROTO:
|
||||
sc->vtnet_stats.rx_csum_bad_ipproto++;
|
||||
break;
|
||||
}
|
||||
else
|
||||
rxq->vtnrx_stats.vrxs_csum_failed++;
|
||||
}
|
||||
#else
|
||||
sc->vtnet_stats.rx_csum_bad_ethtype++;
|
||||
rxq->vtnrx_stats.vrxs_csum_failed++;
|
||||
@@ -2473,6 +2587,166 @@ vtnet_txq_free_mbufs(struct vtnet_txq *txq)
|
||||
("%s: mbufs remaining in tx queue %p", __func__, txq));
|
||||
}
|
||||
|
||||
/*
|
||||
* BMV: This can go away once we finally have offsets in the mbuf header.
|
||||
*/
|
||||
static int
|
||||
vtnet_txq_offload_ctx(struct vtnet_txq *txq, struct mbuf *m, int *etype,
|
||||
int *proto, int *start)
|
||||
{
|
||||
struct vtnet_softc *sc;
|
||||
struct ether_vlan_header *evh;
|
||||
#if defined(INET) || defined(INET6)
|
||||
int offset;
|
||||
#endif
|
||||
|
||||
sc = txq->vtntx_sc;
|
||||
|
||||
evh = mtod(m, struct ether_vlan_header *);
|
||||
if (evh->evl_encap_proto == htons(ETHERTYPE_VLAN)) {
|
||||
/* BMV: We should handle nested VLAN tags too. */
|
||||
*etype = ntohs(evh->evl_proto);
|
||||
#if defined(INET) || defined(INET6)
|
||||
offset = sizeof(struct ether_vlan_header);
|
||||
#endif
|
||||
} else {
|
||||
*etype = ntohs(evh->evl_encap_proto);
|
||||
#if defined(INET) || defined(INET6)
|
||||
offset = sizeof(struct ether_header);
|
||||
#endif
|
||||
}
|
||||
|
||||
switch (*etype) {
|
||||
#if defined(INET)
|
||||
case ETHERTYPE_IP: {
|
||||
struct ip *ip, iphdr;
|
||||
if (__predict_false(m->m_len < offset + sizeof(struct ip))) {
|
||||
m_copydata(m, offset, sizeof(struct ip),
|
||||
(caddr_t) &iphdr);
|
||||
ip = &iphdr;
|
||||
} else
|
||||
ip = (struct ip *)(m->m_data + offset);
|
||||
*proto = ip->ip_p;
|
||||
*start = offset + (ip->ip_hl << 2);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#if defined(INET6)
|
||||
case ETHERTYPE_IPV6:
|
||||
*proto = -1;
|
||||
*start = ip6_lasthdr(m, offset, IPPROTO_IPV6, proto);
|
||||
/* Assert the network stack sent us a valid packet. */
|
||||
KASSERT(*start > offset,
|
||||
("%s: mbuf %p start %d offset %d proto %d", __func__, m,
|
||||
*start, offset, *proto));
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
sc->vtnet_stats.tx_csum_unknown_ethtype++;
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
vtnet_txq_offload_tso(struct vtnet_txq *txq, struct mbuf *m, int eth_type,
|
||||
int offset, struct virtio_net_hdr *hdr)
|
||||
{
|
||||
static struct timeval lastecn;
|
||||
static int curecn;
|
||||
struct vtnet_softc *sc;
|
||||
struct tcphdr *tcp, tcphdr;
|
||||
|
||||
sc = txq->vtntx_sc;
|
||||
|
||||
if (__predict_false(m->m_len < offset + sizeof(struct tcphdr))) {
|
||||
m_copydata(m, offset, sizeof(struct tcphdr), (caddr_t) &tcphdr);
|
||||
tcp = &tcphdr;
|
||||
} else
|
||||
tcp = (struct tcphdr *)(m->m_data + offset);
|
||||
|
||||
hdr->hdr_len = vtnet_gtoh16(sc, offset + (tcp->th_off << 2));
|
||||
hdr->gso_size = vtnet_gtoh16(sc, m->m_pkthdr.tso_segsz);
|
||||
hdr->gso_type = eth_type == ETHERTYPE_IP ? VIRTIO_NET_HDR_GSO_TCPV4 :
|
||||
VIRTIO_NET_HDR_GSO_TCPV6;
|
||||
|
||||
if (__predict_false(tcp_get_flags(tcp) & TH_CWR)) {
|
||||
/*
|
||||
* Drop if VIRTIO_NET_F_HOST_ECN was not negotiated. In
|
||||
* FreeBSD, ECN support is not on a per-interface basis,
|
||||
* but globally via the net.inet.tcp.ecn.enable sysctl
|
||||
* knob. The default is off.
|
||||
*/
|
||||
if ((sc->vtnet_flags & VTNET_FLAG_TSO_ECN) == 0) {
|
||||
if (ppsratecheck(&lastecn, &curecn, 1))
|
||||
if_printf(sc->vtnet_ifp,
|
||||
"TSO with ECN not negotiated with host\n");
|
||||
return (ENOTSUP);
|
||||
}
|
||||
hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN;
|
||||
}
|
||||
|
||||
txq->vtntx_stats.vtxs_tso++;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static struct mbuf *
|
||||
vtnet_txq_offload(struct vtnet_txq *txq, struct mbuf *m,
|
||||
struct virtio_net_hdr *hdr)
|
||||
{
|
||||
struct vtnet_softc *sc;
|
||||
int flags, etype, csum_start, proto, error;
|
||||
|
||||
sc = txq->vtntx_sc;
|
||||
flags = m->m_pkthdr.csum_flags;
|
||||
|
||||
error = vtnet_txq_offload_ctx(txq, m, &etype, &proto, &csum_start);
|
||||
if (error)
|
||||
goto drop;
|
||||
|
||||
if (flags & (VTNET_CSUM_OFFLOAD | VTNET_CSUM_OFFLOAD_IPV6)) {
|
||||
/* Sanity check the parsed mbuf matches the offload flags. */
|
||||
if (__predict_false((flags & VTNET_CSUM_OFFLOAD &&
|
||||
etype != ETHERTYPE_IP) || (flags & VTNET_CSUM_OFFLOAD_IPV6
|
||||
&& etype != ETHERTYPE_IPV6))) {
|
||||
sc->vtnet_stats.tx_csum_proto_mismatch++;
|
||||
goto drop;
|
||||
}
|
||||
|
||||
hdr->flags |= VIRTIO_NET_HDR_F_NEEDS_CSUM;
|
||||
hdr->csum_start = vtnet_gtoh16(sc, csum_start);
|
||||
hdr->csum_offset = vtnet_gtoh16(sc, m->m_pkthdr.csum_data);
|
||||
txq->vtntx_stats.vtxs_csum++;
|
||||
}
|
||||
|
||||
if (flags & (CSUM_IP_TSO | CSUM_IP6_TSO)) {
|
||||
/*
|
||||
* Sanity check the parsed mbuf IP protocol is TCP, and
|
||||
* VirtIO TSO reqires the checksum offloading above.
|
||||
*/
|
||||
if (__predict_false(proto != IPPROTO_TCP)) {
|
||||
sc->vtnet_stats.tx_tso_not_tcp++;
|
||||
goto drop;
|
||||
} else if (__predict_false((hdr->flags &
|
||||
VIRTIO_NET_HDR_F_NEEDS_CSUM) == 0)) {
|
||||
sc->vtnet_stats.tx_tso_without_csum++;
|
||||
goto drop;
|
||||
}
|
||||
|
||||
error = vtnet_txq_offload_tso(txq, m, etype, csum_start, hdr);
|
||||
if (error)
|
||||
goto drop;
|
||||
}
|
||||
|
||||
return (m);
|
||||
|
||||
drop:
|
||||
m_freem(m);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
vtnet_txq_enqueue_callback(void *arg, bus_dma_segment_t *segs,
|
||||
int nsegs, int error)
|
||||
@@ -2645,38 +2919,11 @@ vtnet_txq_encap(struct vtnet_txq *txq, struct mbuf **m_head, int flags)
|
||||
}
|
||||
|
||||
if (m->m_pkthdr.csum_flags & VTNET_CSUM_ALL_OFFLOAD) {
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Translate the CSUM_* flags in the mbuf to the corresponding
|
||||
* flags in the VirtIO header.
|
||||
*/
|
||||
ret = virtio_net_tx_offload(txq->vtntx_sc->vtnet_ifp, &m, hdr,
|
||||
(txq->vtntx_sc->vtnet_flags & VTNET_FLAG_TSO_ECN),
|
||||
vtnet_modern(txq->vtntx_sc));
|
||||
switch (ret) {
|
||||
case VIRTIO_NET_TX_OFFLOAD_UNKNOWN_ETHTYPE:
|
||||
txq->vtntx_sc->vtnet_stats.tx_csum_unknown_ethtype++;
|
||||
break;
|
||||
case VIRTIO_NET_TX_OFFLOAD_PROTO_MISMATCH:
|
||||
txq->vtntx_sc->vtnet_stats.tx_csum_proto_mismatch++;
|
||||
break;
|
||||
case VIRTIO_NET_TX_OFFLOAD_TSO_NOT_TCP:
|
||||
txq->vtntx_sc->vtnet_stats.tx_tso_not_tcp++;
|
||||
break;
|
||||
case VIRTIO_NET_TX_OFFLOAD_TSO_WITHOUT_CSUM:
|
||||
txq->vtntx_sc->vtnet_stats.tx_tso_without_csum++;
|
||||
break;
|
||||
}
|
||||
m = vtnet_txq_offload(txq, m, hdr);
|
||||
if ((*m_head = m) == NULL) {
|
||||
error = ENOBUFS;
|
||||
goto fail;
|
||||
}
|
||||
if (m->m_pkthdr.csum_flags &
|
||||
(VTNET_CSUM_OFFLOAD | VTNET_CSUM_OFFLOAD_IPV6))
|
||||
txq->vtntx_stats.vtxs_csum++;
|
||||
if (m->m_pkthdr.csum_flags & (CSUM_IP_TSO | CSUM_IP6_TSO))
|
||||
txq->vtntx_stats.vtxs_tso++;
|
||||
}
|
||||
|
||||
error = vtnet_txq_enqueue_buf(txq, m_head, txhdr);
|
||||
|
||||
+165
-192
@@ -31,11 +31,6 @@
|
||||
#ifndef _VIRTIO_NET_H
|
||||
#define _VIRTIO_NET_H
|
||||
|
||||
#include "opt_inet.h"
|
||||
#include "opt_inet6.h"
|
||||
|
||||
#include <sys/endian.h>
|
||||
|
||||
/* The feature bitmap for virtio net */
|
||||
#define VIRTIO_NET_F_CSUM (1ULL << 0) /* Host handles pkts w/ partial csum */
|
||||
#define VIRTIO_NET_F_GUEST_CSUM (1ULL << 1) /* Guest handles pkts w/ partial csum*/
|
||||
@@ -265,150 +260,161 @@ struct virtio_net_ctrl_mq {
|
||||
#define VIRTIO_NET_CTRL_GUEST_OFFLOADS 5
|
||||
#define VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET 0
|
||||
|
||||
#if defined(INET) || defined(INET6)
|
||||
static inline void
|
||||
virtio_net_rx_csum_needs_csum(struct mbuf *m, bool isipv6, int protocol,
|
||||
struct virtio_net_hdr *hdr)
|
||||
{
|
||||
/*
|
||||
* 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));
|
||||
|
||||
/*
|
||||
* Just forward the order to compute the checksum by setting
|
||||
* the corresponding mbuf flag (e.g., CSUM_TCP).
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
static inline void
|
||||
virtio_net_rx_csum_data_valid(struct mbuf *m, int protocol)
|
||||
{
|
||||
KASSERT(protocol == IPPROTO_TCP || protocol == IPPROTO_UDP,
|
||||
("%s: unsupported IP protocol %d", __func__, protocol));
|
||||
|
||||
m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
|
||||
m->m_pkthdr.csum_data = 0xFFFF;
|
||||
}
|
||||
|
||||
#define VIRTIO_NET_RX_CSUM_INACCESSIBLE_IPPROTO 1
|
||||
#define VIRTIO_NET_RX_CSUM_BAD_ETHTYPE 2
|
||||
#define VIRTIO_NET_RX_CSUM_BAD_IPPROTO 3
|
||||
|
||||
/*
|
||||
* For a packet received over the VirtIO channel, it checks the given
|
||||
* VirtIO header and sets the appropriate CSUM_* flags in the given mbuf.
|
||||
*
|
||||
* Unfortunately, the information provided is not directly useful to us. The
|
||||
* VirtIO header gives the offset of the checksum, which is all Linux needs, but
|
||||
* this is not how FreeBSD does things. We are forced to peek inside the packet
|
||||
* a bit.
|
||||
*
|
||||
* It would be nice if VirtIO gave us the L4 protocol or if FreeBSD
|
||||
* could accept the offsets and let the stack figure it out.
|
||||
*
|
||||
* @param m mbuf of the packet where CSUM_* flags might need to be set.
|
||||
* @param hdr VirtIO header of the received packet that needs to be checked
|
||||
* with its field values stored in the byte order this machine
|
||||
* uses (i.e., readable without a byte swap).
|
||||
*
|
||||
* @return 0 on success, or one of the VIRTIO_NET_RX_CSUM_* error codes.
|
||||
* Use the checksum offset in the VirtIO header to set the
|
||||
* correct CSUM_* flags.
|
||||
*/
|
||||
static inline int
|
||||
virtio_net_rx_csum(struct mbuf *m, struct virtio_net_hdr *hdr)
|
||||
virtio_net_rx_csum_by_offset(struct mbuf *m, uint16_t eth_type, int ip_start,
|
||||
struct virtio_net_hdr *hdr)
|
||||
{
|
||||
const struct ether_header *eh;
|
||||
int hoff, protocol;
|
||||
uint16_t etype;
|
||||
bool isipv6;
|
||||
#if defined(INET) || defined(INET6)
|
||||
int offset = hdr->csum_start + hdr->csum_offset;
|
||||
#endif
|
||||
|
||||
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);
|
||||
if (etype == ETHERTYPE_VLAN) {
|
||||
/* TODO BMV: Handle QinQ. */
|
||||
const struct ether_vlan_header *evh =
|
||||
mtod(m, const struct ether_vlan_header *);
|
||||
etype = ntohs(evh->evl_proto);
|
||||
hoff = sizeof(struct ether_vlan_header);
|
||||
} else
|
||||
hoff = sizeof(struct ether_header);
|
||||
|
||||
/* Check whether ethernet type is IP or IPv6, and get protocol. */
|
||||
switch (etype) {
|
||||
/* Only do a basic sanity check on the offset. */
|
||||
switch (eth_type) {
|
||||
#if defined(INET)
|
||||
case ETHERTYPE_IP:
|
||||
if (__predict_false(m->m_len < hoff + sizeof(struct ip))) {
|
||||
return (VIRTIO_NET_RX_CSUM_INACCESSIBLE_IPPROTO);
|
||||
} else {
|
||||
struct ip *ip = (struct ip *)(m->m_data + hoff);
|
||||
protocol = ip->ip_p;
|
||||
}
|
||||
isipv6 = false;
|
||||
if (__predict_false(offset < ip_start + sizeof(struct ip)))
|
||||
return (1);
|
||||
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) {
|
||||
return (VIRTIO_NET_RX_CSUM_INACCESSIBLE_IPPROTO);
|
||||
}
|
||||
isipv6 = true;
|
||||
if (__predict_false(offset < ip_start + sizeof(struct ip6_hdr)))
|
||||
return (1);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return (VIRTIO_NET_RX_CSUM_BAD_ETHTYPE);
|
||||
/* Here we should increment the rx_csum_bad_ethtype counter. */
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Check whether protocol is TCP or UDP. */
|
||||
switch (protocol) {
|
||||
case IPPROTO_TCP:
|
||||
case IPPROTO_UDP:
|
||||
/*
|
||||
* Use the offset to determine the appropriate CSUM_* flags. This is
|
||||
* a bit dirty, but we can get by with it since the checksum offsets
|
||||
* happen to be different. We assume the host host does not do IPv4
|
||||
* header checksum offloading.
|
||||
*/
|
||||
switch (hdr->csum_offset) {
|
||||
case offsetof(struct udphdr, uh_sum):
|
||||
case offsetof(struct tcphdr, th_sum):
|
||||
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 here.
|
||||
*/
|
||||
return (VIRTIO_NET_RX_CSUM_BAD_IPPROTO);
|
||||
/* Here we should increment the rx_csum_bad_offset counter. */
|
||||
return (1);
|
||||
}
|
||||
|
||||
if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)
|
||||
virtio_net_rx_csum_needs_csum(m, isipv6, protocol, hdr);
|
||||
else /* VIRTIO_NET_HDR_F_DATA_VALID */
|
||||
virtio_net_rx_csum_data_valid(m, protocol);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static inline int
|
||||
virtio_net_rx_csum_by_parse(struct mbuf *m, uint16_t eth_type, int ip_start,
|
||||
struct virtio_net_hdr *hdr)
|
||||
{
|
||||
int offset, proto;
|
||||
|
||||
switch (eth_type) {
|
||||
#if defined(INET)
|
||||
case ETHERTYPE_IP: {
|
||||
struct ip *ip;
|
||||
if (__predict_false(m->m_len < ip_start + sizeof(struct ip)))
|
||||
return (1);
|
||||
ip = (struct ip *)(m->m_data + ip_start);
|
||||
proto = ip->ip_p;
|
||||
offset = ip_start + (ip->ip_hl << 2);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#if defined(INET6)
|
||||
case ETHERTYPE_IPV6:
|
||||
if (__predict_false(m->m_len < ip_start +
|
||||
sizeof(struct ip6_hdr)))
|
||||
return (1);
|
||||
offset = ip6_lasthdr(m, ip_start, IPPROTO_IPV6, &proto);
|
||||
if (__predict_false(offset < 0))
|
||||
return (1);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
/* Here we should increment the rx_csum_bad_ethtype counter. */
|
||||
return (1);
|
||||
}
|
||||
|
||||
#define VIRTIO_NET_TX_OFFLOAD_UNKNOWN_ETHTYPE 1
|
||||
#define VIRTIO_NET_TX_OFFLOAD_PROTO_MISMATCH 2
|
||||
#define VIRTIO_NET_TX_OFFLOAD_TSO_NOT_TCP 3
|
||||
#define VIRTIO_NET_TX_OFFLOAD_TSO_WITHOUT_CSUM 4
|
||||
#define VIRTIO_NET_TX_OFFLOAD_TSO_ECN_UNEXPECTED 5
|
||||
switch (proto) {
|
||||
case IPPROTO_TCP:
|
||||
if (__predict_false(m->m_len < offset + sizeof(struct tcphdr)))
|
||||
return (1);
|
||||
m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
|
||||
m->m_pkthdr.csum_data = 0xFFFF;
|
||||
break;
|
||||
case IPPROTO_UDP:
|
||||
if (__predict_false(m->m_len < offset + sizeof(struct udphdr)))
|
||||
return (1);
|
||||
m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
|
||||
m->m_pkthdr.csum_data = 0xFFFF;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* For the remaining protocols, FreeBSD does not support
|
||||
* checksum offloading, so the checksum will be recomputed.
|
||||
*/
|
||||
#if 0
|
||||
if_printf(ifp, "cksum offload of unsupported "
|
||||
"protocol eth_type=%#x proto=%d csum_start=%d "
|
||||
"csum_offset=%d\n", __func__, eth_type, proto,
|
||||
hdr->csum_start, hdr->csum_offset);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
#define VIRTIO_NET_TX_MODERN_LE(modern, val) (modern ? htole16(val) : val)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* BMV: This can go away once we finally have offsets in the mbuf header.
|
||||
* Set the appropriate CSUM_* flags. Unfortunately, the information
|
||||
* provided is not directly useful to us. The VirtIO header gives the
|
||||
* offset of the checksum, which is all Linux needs, but this is not
|
||||
* how FreeBSD does things. We are forced to peek inside the packet
|
||||
* a bit.
|
||||
*
|
||||
* It would be nice if VirtIO gave us the L4 protocol or if FreeBSD
|
||||
* could accept the offsets and let the stack figure it out.
|
||||
*/
|
||||
static inline int
|
||||
virtio_net_rx_csum(struct mbuf *m, struct virtio_net_hdr *hdr)
|
||||
{
|
||||
struct ether_header *eh;
|
||||
struct ether_vlan_header *evh;
|
||||
uint16_t eth_type;
|
||||
int offset, error;
|
||||
|
||||
if ((hdr->flags & (VIRTIO_NET_HDR_F_NEEDS_CSUM |
|
||||
VIRTIO_NET_HDR_F_DATA_VALID)) == 0) {
|
||||
return (0);
|
||||
}
|
||||
|
||||
eh = mtod(m, struct ether_header *);
|
||||
eth_type = ntohs(eh->ether_type);
|
||||
if (eth_type == ETHERTYPE_VLAN) {
|
||||
/* BMV: We should handle nested VLAN tags too. */
|
||||
evh = mtod(m, struct ether_vlan_header *);
|
||||
eth_type = ntohs(evh->evl_proto);
|
||||
offset = sizeof(struct ether_vlan_header);
|
||||
} else
|
||||
offset = sizeof(struct ether_header);
|
||||
|
||||
if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)
|
||||
error = virtio_net_rx_csum_by_offset(m, eth_type, offset, hdr);
|
||||
else
|
||||
error = virtio_net_rx_csum_by_parse(m, eth_type, offset, hdr);
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
static inline int
|
||||
virtio_net_tx_offload_ctx(struct mbuf *m, int *etype, int *proto, int *start)
|
||||
{
|
||||
@@ -457,15 +463,16 @@ virtio_net_tx_offload_ctx(struct mbuf *m, int *etype, int *proto, int *start)
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return (VIRTIO_NET_TX_OFFLOAD_UNKNOWN_ETHTYPE);
|
||||
/* Here we should increment the tx_csum_bad_ethtype counter. */
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static inline int
|
||||
virtio_net_tx_offload_tso(struct ifnet *ifp, struct mbuf *m, int eth_type,
|
||||
int offset, struct virtio_net_hdr *hdr, bool tso_ecn, bool modern)
|
||||
virtio_net_tx_offload_tso(if_t ifp, struct mbuf *m, int eth_type,
|
||||
int offset, bool allow_ecn, struct virtio_net_hdr *hdr)
|
||||
{
|
||||
static struct timeval lastecn;
|
||||
static int curecn;
|
||||
@@ -477,113 +484,79 @@ virtio_net_tx_offload_tso(struct ifnet *ifp, struct mbuf *m, int eth_type,
|
||||
} else
|
||||
tcp = (struct tcphdr *)(m->m_data + offset);
|
||||
|
||||
/*
|
||||
* Set VirtIO header fields with the correct byte order.
|
||||
* In modern mode, this is little endian (LE).
|
||||
* In legacy mode, this is the endianness of the guest, which means a
|
||||
* FreeBSD guest can use its native endianness and a host must use the
|
||||
* guests endianness. However, since a FreeBSD host with bhyve runs only
|
||||
* on LE systems and supports only LE guests, no conversion is required.
|
||||
*/
|
||||
hdr->hdr_len = VIRTIO_NET_TX_MODERN_LE(modern,
|
||||
offset + (tcp->th_off << 2));
|
||||
hdr->gso_size = VIRTIO_NET_TX_MODERN_LE(modern, m->m_pkthdr.tso_segsz);
|
||||
hdr->hdr_len = offset + (tcp->th_off << 2);
|
||||
hdr->gso_size = m->m_pkthdr.tso_segsz;
|
||||
hdr->gso_type = eth_type == ETHERTYPE_IP ? VIRTIO_NET_HDR_GSO_TCPV4 :
|
||||
VIRTIO_NET_HDR_GSO_TCPV6;
|
||||
|
||||
if (__predict_false(tcp_get_flags(tcp) & TH_CWR)) {
|
||||
if (tcp_get_flags(tcp) & TH_CWR) {
|
||||
/*
|
||||
* Drop if VIRTIO_NET_F_HOST_ECN was not negotiated. In
|
||||
* FreeBSD, ECN support is not on a per-interface basis,
|
||||
* but globally via the net.inet.tcp.ecn.enable sysctl
|
||||
* knob. The default is off.
|
||||
* Drop if VIRTIO_NET_F_HOST_ECN was not negotiated. In FreeBSD,
|
||||
* ECN support is not on a per-interface basis, but globally via
|
||||
* the net.inet.tcp.ecn.enable sysctl knob. The default is off.
|
||||
*/
|
||||
if (!tso_ecn) {
|
||||
if (!allow_ecn) {
|
||||
if (ppsratecheck(&lastecn, &curecn, 1))
|
||||
if_printf(ifp,
|
||||
"TSO with ECN not negotiated with host\n");
|
||||
return (VIRTIO_NET_TX_OFFLOAD_TSO_ECN_UNEXPECTED);
|
||||
return (ENOTSUP);
|
||||
}
|
||||
hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN;
|
||||
}
|
||||
|
||||
/* Here we should increment tx_tso counter. */
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* For a packet to be transmitted over the VirtIO channel, it checks the
|
||||
* CSUM_* flags in the mbuf and sets the appropriate flags in the VirtIO header.
|
||||
* In case of an error, it frees the mbuf and sets the pointer referenced by mp
|
||||
* to NULL.
|
||||
*
|
||||
* @param ifp ifnet struct of outgoing interface.
|
||||
* @param mp mbuf on which the CSUM_* flags needs to be checked.
|
||||
* @param hdr VirtIO header to be filled for the outgoing packet.
|
||||
* @param tso_ecn true if ECN has been negotiated between host and guest.
|
||||
* @param modern true if VirtIO modern mode is used.
|
||||
*
|
||||
* @return 0 on success, or one of the VIRTIO_NET_TX_OFFLOAD_* error codes.
|
||||
*/
|
||||
static inline int
|
||||
virtio_net_tx_offload(struct ifnet *ifp, struct mbuf **mp,
|
||||
struct virtio_net_hdr *hdr, bool tso_ecn, bool modern)
|
||||
static inline struct mbuf *
|
||||
virtio_net_tx_offload(if_t ifp, struct mbuf *m, bool allow_ecn,
|
||||
struct virtio_net_hdr *hdr)
|
||||
{
|
||||
int flags, etype, csum_start, proto, error;
|
||||
struct mbuf *m;
|
||||
|
||||
m = *mp;
|
||||
flags = m->m_pkthdr.csum_flags;
|
||||
|
||||
error = virtio_net_tx_offload_ctx(m, &etype, &proto, &csum_start);
|
||||
if (error != 0)
|
||||
if (error)
|
||||
goto drop;
|
||||
|
||||
if (flags & (CSUM_TCP | CSUM_UDP | CSUM_TCP_IPV6 | CSUM_UDP_IPV6)) {
|
||||
/* Sanity check the parsed mbuf matches the offload flags. */
|
||||
if (__predict_false((flags & (CSUM_TCP | CSUM_UDP) &&
|
||||
etype != ETHERTYPE_IP) ||
|
||||
(flags & (CSUM_TCP_IPV6 | CSUM_UDP_IPV6) &&
|
||||
etype != ETHERTYPE_IPV6))) {
|
||||
error = VIRTIO_NET_TX_OFFLOAD_PROTO_MISMATCH;
|
||||
goto drop;
|
||||
}
|
||||
|
||||
if ((etype == ETHERTYPE_IP && (flags & (CSUM_TCP | CSUM_UDP))) ||
|
||||
(etype == ETHERTYPE_IPV6 &&
|
||||
(flags & (CSUM_TCP_IPV6 | CSUM_UDP_IPV6)))) {
|
||||
/*
|
||||
* Set VirtIO header fields with the correct byte order.
|
||||
* See comment in virtio_net_tx_offload_tso()
|
||||
* We could compare the IP protocol vs the CSUM_ flag too,
|
||||
* but that really should not be necessary.
|
||||
*/
|
||||
hdr->flags |= VIRTIO_NET_HDR_F_NEEDS_CSUM;
|
||||
hdr->csum_start = VIRTIO_NET_TX_MODERN_LE(modern, csum_start);
|
||||
hdr->csum_offset = VIRTIO_NET_TX_MODERN_LE(modern,
|
||||
m->m_pkthdr.csum_data);
|
||||
hdr->csum_start = csum_start;
|
||||
hdr->csum_offset = m->m_pkthdr.csum_data;
|
||||
/* Here we should increment the tx_csum counter. */
|
||||
}
|
||||
|
||||
if (flags & (CSUM_IP_TSO | CSUM_IP6_TSO)) {
|
||||
/*
|
||||
* Sanity check the parsed mbuf IP protocol is TCP, and
|
||||
* VirtIO TSO reqires the checksum offloading above.
|
||||
*/
|
||||
if (flags & CSUM_TSO) {
|
||||
if (__predict_false(proto != IPPROTO_TCP)) {
|
||||
error = VIRTIO_NET_TX_OFFLOAD_TSO_NOT_TCP;
|
||||
goto drop;
|
||||
} else if (__predict_false((hdr->flags &
|
||||
VIRTIO_NET_HDR_F_NEEDS_CSUM) == 0)) {
|
||||
error = VIRTIO_NET_TX_OFFLOAD_TSO_WITHOUT_CSUM;
|
||||
/* Likely failed to correctly parse the mbuf.
|
||||
* Here we should increment the tx_tso_not_tcp
|
||||
* counter. */
|
||||
goto drop;
|
||||
}
|
||||
|
||||
KASSERT(hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
("%s: mbuf %p TSO without checksum offload %#x",
|
||||
__func__, m, flags));
|
||||
|
||||
error = virtio_net_tx_offload_tso(ifp, m, etype, csum_start,
|
||||
hdr, tso_ecn, modern);
|
||||
if (error != 0)
|
||||
allow_ecn, hdr);
|
||||
if (error)
|
||||
goto drop;
|
||||
}
|
||||
|
||||
return (error);
|
||||
return (m);
|
||||
|
||||
drop:
|
||||
m_freem(m);
|
||||
*mp = NULL;
|
||||
return (error);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
#endif /* _VIRTIO_NET_H */
|
||||
+2
-17
@@ -1882,16 +1882,7 @@ tunread(struct cdev *dev, struct uio *uio, int flag)
|
||||
|
||||
bzero(&vhdr, sizeof(vhdr));
|
||||
if (m->m_pkthdr.csum_flags & TAP_ALL_OFFLOAD) {
|
||||
/*
|
||||
* Translate the CSUM_* flags in the mbuf to the
|
||||
* corresponding flags in the VirtIO header.
|
||||
*
|
||||
* Always indicate that ECN has not been negotiated
|
||||
* and VirtIO modern mode is not used because bhyve
|
||||
* does not do this.
|
||||
*/
|
||||
virtio_net_tx_offload(ifp, &m, &vhdr.hdr, false,
|
||||
false);
|
||||
m = virtio_net_tx_offload(ifp, m, false, &vhdr.hdr);
|
||||
}
|
||||
|
||||
TUNDEBUG(ifp, "txvhdr: f %u, gt %u, hl %u, "
|
||||
@@ -1936,13 +1927,7 @@ tunwrite_l2(struct tuntap_softc *tp, struct mbuf *m,
|
||||
}
|
||||
|
||||
if (vhdr != NULL) {
|
||||
/*
|
||||
* Translate the VirtIO header flags to the corresponding
|
||||
* CSUM_* flags in the mbuf.
|
||||
*/
|
||||
if (((vhdr->hdr.flags & (VIRTIO_NET_HDR_F_NEEDS_CSUM |
|
||||
VIRTIO_NET_HDR_F_DATA_VALID)) != 0) &&
|
||||
(virtio_net_rx_csum(m, &vhdr->hdr) != 0)) {
|
||||
if (virtio_net_rx_csum(m, &vhdr->hdr)) {
|
||||
m_freem(m);
|
||||
return (0);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user