#if defined(__FreeBSD__)
#include <sys/cdefs.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/sockio.h>
#include <sys/malloc.h>
#include <sys/socketvar.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_var.h>
#include <machine/bus.h>
#include <sys/endian.h>
#elif defined(linux)
#include "bsd_glue.h"
#elif defined(__APPLE__)
#warning OSX support is only partial
#include "osx_glue.h"
#else
#error Unsupported platform
#endif
#include <net/netmap.h>
#include <dev/netmap/netmap_kern.h>
static void
gso_fix_segment(uint8_t *pkt, size_t len, u_int ipv4, u_int iphlen, u_int tcp,
u_int idx, u_int segmented_bytes, u_int last_segment)
{
struct nm_iphdr *iph = (struct nm_iphdr *)(pkt);
struct nm_ipv6hdr *ip6h = (struct nm_ipv6hdr *)(pkt);
uint16_t *check = NULL;
uint8_t *check_data = NULL;
if (ipv4) {
iph->tot_len = htobe16(len);
nm_prdis("ip total length %u", be16toh(ip->tot_len));
iph->id = htobe16(be16toh(iph->id) + idx);
nm_prdis("ip identification %u", be16toh(iph->id));
iph->check = 0;
iph->check = nm_os_csum_ipv4(iph);
nm_prdis("IP csum %x", be16toh(iph->check));
} else {
ip6h->payload_len = htobe16(len-iphlen);
}
if (tcp) {
struct nm_tcphdr *tcph = (struct nm_tcphdr *)(pkt + iphlen);
tcph->seq = htobe32(be32toh(tcph->seq) + segmented_bytes);
nm_prdis("tcp seq %u", be32toh(tcph->seq));
if (!last_segment)
tcph->flags &= ~(0x8 | 0x1);
nm_prdis("last_segment %u", last_segment);
check = &tcph->check;
check_data = (uint8_t *)tcph;
} else {
struct nm_udphdr *udph = (struct nm_udphdr *)(pkt + iphlen);
udph->len = htobe16(len-iphlen);
check = &udph->check;
check_data = (uint8_t *)udph;
}
*check = 0;
if (ipv4)
nm_os_csum_tcpudp_ipv4(iph, check_data, len-iphlen, check);
else
nm_os_csum_tcpudp_ipv6(ip6h, check_data, len-iphlen, check);
nm_prdis("TCP/UDP csum %x", be16toh(*check));
}
static inline int
vnet_hdr_is_bad(struct nm_vnet_hdr *vh)
{
uint8_t gso_type = vh->gso_type & ~VIRTIO_NET_HDR_GSO_ECN;
return (
(gso_type != VIRTIO_NET_HDR_GSO_NONE &&
gso_type != VIRTIO_NET_HDR_GSO_TCPV4 &&
gso_type != VIRTIO_NET_HDR_GSO_UDP &&
gso_type != VIRTIO_NET_HDR_GSO_TCPV6)
||
(vh->flags & ~(VIRTIO_NET_HDR_F_NEEDS_CSUM
| VIRTIO_NET_HDR_F_DATA_VALID))
);
}
void
bdg_mismatch_datapath(struct netmap_vp_adapter *na,
struct netmap_vp_adapter *dst_na,
const struct nm_bdg_fwd *ft_p,
struct netmap_ring *dst_ring,
u_int *j, u_int lim, u_int *howmany)
{
struct netmap_slot *dst_slot = NULL;
struct nm_vnet_hdr *vh = NULL;
const struct nm_bdg_fwd *ft_end = ft_p + ft_p->ft_frags;
uint8_t *dst, *src;
size_t src_len, dst_len;
u_int j_start = *j;
u_int j_cur = j_start;
u_int dst_slots = 0;
if (unlikely(ft_p == ft_end)) {
nm_prlim(1, "No source slots to process");
return;
}
src = ft_p->ft_buf;
src_len = ft_p->ft_len;
dst_slot = &dst_ring->slot[j_cur];
dst = NMB(&dst_na->up, dst_slot);
dst_len = src_len;
if (na->up.virt_hdr_len && !dst_na->up.virt_hdr_len) {
vh = (struct nm_vnet_hdr *)src;
if (src_len < na->up.virt_hdr_len) {
nm_prlim(1, "Short src vnet header, dropping");
return;
}
if (unlikely(vnet_hdr_is_bad(vh))) {
nm_prlim(1, "Bad src vnet header, dropping");
return;
}
}
bzero(dst, dst_na->up.virt_hdr_len);
if (na->up.virt_hdr_len && dst_na->up.virt_hdr_len)
memcpy(dst, src, sizeof(struct nm_vnet_hdr));
src += na->up.virt_hdr_len;
src_len -= na->up.virt_hdr_len;
dst += dst_na->up.virt_hdr_len;
dst_len = dst_na->up.virt_hdr_len + src_len;
if (dst_len == 0) {
ft_p++;
src = ft_p->ft_buf;
src_len = ft_p->ft_len;
dst_len = src_len;
}
if (vh && vh->gso_type != VIRTIO_NET_HDR_GSO_NONE) {
u_int gso_bytes = 0;
u_int gso_hdr_len = 0;
uint8_t *gso_hdr = NULL;
u_int gso_idx = 0;
u_int segmented_bytes = 0;
u_int ipv4 = 0;
u_int iphlen = 0;
u_int ethhlen = 14;
u_int tcp = ((vh->gso_type & ~VIRTIO_NET_HDR_GSO_ECN)
== VIRTIO_NET_HDR_GSO_UDP) ? 0 : 1;
for (;;) {
size_t copy;
if (dst_slots >= *howmany) {
nm_prdis(1, "Not enough slots, dropping GSO packet");
return;
}
if (!gso_hdr) {
uint16_t ethertype;
gso_hdr = src;
for (;;) {
if (src_len < ethhlen) {
nm_prlim(1, "Short GSO fragment [eth], dropping");
return;
}
ethertype = be16toh(*((uint16_t *)
(gso_hdr + ethhlen - 2)));
if (ethertype != 0x8100)
break;
ethhlen += 4;
}
switch (ethertype) {
case 0x0800:
{
struct nm_iphdr *iph = (struct nm_iphdr *)
(gso_hdr + ethhlen);
if (src_len < ethhlen + 20) {
nm_prlim(1, "Short GSO fragment "
"[IPv4], dropping");
return;
}
ipv4 = 1;
iphlen = 4 * (iph->version_ihl & 0x0F);
break;
}
case 0x86DD:
ipv4 = 0;
iphlen = 40;
break;
default:
nm_prlim(1, "Unsupported ethertype, "
"dropping GSO packet");
return;
}
nm_prdis(3, "type=%04x", ethertype);
if (src_len < ethhlen + iphlen) {
nm_prlim(1, "Short GSO fragment [IP], dropping");
return;
}
if (tcp) {
struct nm_tcphdr *tcph = (struct nm_tcphdr *)
(gso_hdr + ethhlen + iphlen);
if (src_len < ethhlen + iphlen + 20) {
nm_prlim(1, "Short GSO fragment "
"[TCP], dropping");
return;
}
gso_hdr_len = ethhlen + iphlen +
4 * (tcph->doff >> 4);
} else {
gso_hdr_len = ethhlen + iphlen + 8;
}
if (src_len < gso_hdr_len) {
nm_prlim(1, "Short GSO fragment [TCP/UDP], dropping");
return;
}
nm_prdis(3, "gso_hdr_len %u gso_mtu %d", gso_hdr_len,
dst_na->mfs);
src += gso_hdr_len;
src_len -= gso_hdr_len;
if (src_len == 0) {
ft_p++;
if (ft_p == ft_end)
break;
src = ft_p->ft_buf;
src_len = ft_p->ft_len;
}
}
if (gso_bytes == 0) {
memcpy(dst, gso_hdr, gso_hdr_len);
gso_bytes = gso_hdr_len;
}
copy = src_len;
if (gso_bytes + copy > dst_na->mfs)
copy = dst_na->mfs - gso_bytes;
memcpy(dst + gso_bytes, src, copy);
gso_bytes += copy;
src += copy;
src_len -= copy;
if (gso_bytes >= dst_na->mfs ||
(src_len == 0 && ft_p + 1 == ft_end)) {
gso_fix_segment(dst + ethhlen, gso_bytes - ethhlen,
ipv4, iphlen, tcp,
gso_idx, segmented_bytes,
src_len == 0 && ft_p + 1 == ft_end);
nm_prdis("frame %u completed with %d bytes", gso_idx, (int)gso_bytes);
dst_slot->len = gso_bytes;
dst_slot->flags = 0;
dst_slots++;
segmented_bytes += gso_bytes - gso_hdr_len;
gso_bytes = 0;
gso_idx++;
j_cur = nm_next(j_cur, lim);
dst_slot = &dst_ring->slot[j_cur];
dst = NMB(&dst_na->up, dst_slot);
}
if (src_len == 0) {
ft_p++;
if (ft_p == ft_end)
break;
src = ft_p->ft_buf;
src_len = ft_p->ft_len;
}
}
nm_prdis(3, "%d bytes segmented", segmented_bytes);
} else {
uint16_t *check = NULL;
rawsum_t csum = 0;
if (vh && (vh->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)) {
if (unlikely(vh->csum_offset + vh->csum_start > src_len))
nm_prerr("invalid checksum request");
else
check = (uint16_t *)(dst + vh->csum_start +
vh->csum_offset);
}
while (ft_p != ft_end) {
if (vh && (vh->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)) {
if (!dst_slots)
csum = nm_os_csum_raw(src + vh->csum_start,
src_len - vh->csum_start, 0);
else
csum = nm_os_csum_raw(src, src_len, csum);
}
src_len = (src_len + 63) & ~63;
if (ft_p->ft_flags & NS_INDIRECT) {
if (copyin(src, dst, src_len)) {
dst_len = 0;
}
} else {
memcpy(dst, src, (int)src_len);
}
dst_slot->len = dst_len;
dst_slots++;
j_cur = nm_next(j_cur, lim);
dst_slot = &dst_ring->slot[j_cur];
dst = NMB(&dst_na->up, dst_slot);
ft_p++;
src = ft_p->ft_buf;
dst_len = src_len = ft_p->ft_len;
}
if (check && vh && (vh->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)) {
*check = nm_os_csum_fold(csum);
}
nm_prdis(3, "using %u dst_slots", dst_slots);
while (j_start != j_cur) {
dst_slot = &dst_ring->slot[j_start];
dst_slot->flags = (dst_slots << 8)| NS_MOREFRAG;
j_start = nm_next(j_start, lim);
}
dst_slot->flags = (dst_slots << 8);
}
if (unlikely(dst_slots > *howmany)) {
nm_prerr("bug: slot allocation error");
}
*j = j_cur;
*howmany -= dst_slots;
}