aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/virtio
diff options
context:
space:
mode:
authorVincenzo Maffione <vmaffione@FreeBSD.org>2019-10-18 21:53:27 +0000
committerVincenzo Maffione <vmaffione@FreeBSD.org>2019-10-18 21:53:27 +0000
commitf8bc74e2f4a5178ba3d0f360c1433a005e1a92cd (patch)
tree1250053e4f00e6cd5b68ae1ff91c86c8e9754726 /sys/dev/virtio
parent43e4b6ca7f12df71e2e0cb8618fc221a7f4ca91a (diff)
Notes
Diffstat (limited to 'sys/dev/virtio')
-rw-r--r--sys/dev/virtio/network/virtio_net.h293
1 files changed, 293 insertions, 0 deletions
diff --git a/sys/dev/virtio/network/virtio_net.h b/sys/dev/virtio/network/virtio_net.h
index b0c80b58c732..019193db1cc0 100644
--- a/sys/dev/virtio/network/virtio_net.h
+++ b/sys/dev/virtio/network/virtio_net.h
@@ -201,4 +201,297 @@ struct virtio_net_ctrl_mq {
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN 1
#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX 0x8000
+/*
+ * Use the checksum offset in the VirtIO header to set the
+ * correct CSUM_* flags.
+ */
+static inline int
+virtio_net_rx_csum_by_offset(struct mbuf *m, uint16_t eth_type, int ip_start,
+ struct virtio_net_hdr *hdr)
+{
+#if defined(INET) || defined(INET6)
+ int offset = hdr->csum_start + hdr->csum_offset;
+#endif
+
+ /* Only do a basic sanity check on the offset. */
+ switch (eth_type) {
+#if defined(INET)
+ case ETHERTYPE_IP:
+ if (__predict_false(offset < ip_start + sizeof(struct ip)))
+ return (1);
+ break;
+#endif
+#if defined(INET6)
+ case ETHERTYPE_IPV6:
+ if (__predict_false(offset < ip_start + sizeof(struct ip6_hdr)))
+ return (1);
+ break;
+#endif
+ default:
+ /* Here we should increment the rx_csum_bad_ethtype counter. */
+ return (1);
+ }
+
+ /*
+ * 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:
+ /* Here we should increment the rx_csum_bad_offset counter. */
+ return (1);
+ }
+
+ 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);
+ }
+
+ 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;
+ }
+
+ return (0);
+}
+
+/*
+ * 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)
+{
+ struct ether_vlan_header *evh;
+ int offset;
+
+ 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);
+ offset = sizeof(struct ether_vlan_header);
+ } else {
+ *etype = ntohs(evh->evl_encap_proto);
+ offset = sizeof(struct ether_header);
+ }
+
+ 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:
+ /* Here we should increment the tx_csum_bad_ethtype counter. */
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
+static inline int
+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;
+ struct tcphdr *tcp, tcphdr;
+
+ 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 = 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 (tcp->th_flags & 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 (!allow_ecn) {
+ if (ppsratecheck(&lastecn, &curecn, 1))
+ if_printf(ifp,
+ "TSO with ECN not negotiated with host\n");
+ return (ENOTSUP);
+ }
+ hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN;
+ }
+
+ /* Here we should increment tx_tso counter. */
+
+ return (0);
+}
+
+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;
+
+ flags = m->m_pkthdr.csum_flags;
+
+ error = virtio_net_tx_offload_ctx(m, &etype, &proto, &csum_start);
+ if (error)
+ goto drop;
+
+ if ((etype == ETHERTYPE_IP && (flags & (CSUM_TCP | CSUM_UDP))) ||
+ (etype == ETHERTYPE_IPV6 &&
+ (flags & (CSUM_TCP_IPV6 | CSUM_UDP_IPV6)))) {
+ /*
+ * 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 = csum_start;
+ hdr->csum_offset = m->m_pkthdr.csum_data;
+ /* Here we should increment the tx_csum counter. */
+ }
+
+ if (flags & CSUM_TSO) {
+ if (__predict_false(proto != IPPROTO_TCP)) {
+ /* 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,
+ allow_ecn, hdr);
+ if (error)
+ goto drop;
+ }
+
+ return (m);
+
+drop:
+ m_freem(m);
+ return (NULL);
+}
+
#endif /* _VIRTIO_NET_H */