sunvnet: add support for IPv6 checksum offloads

The original code didn't handle non-IPv4 packets very well, so the
offload advertising had to be scaled back down to just IP.  Here we
add the bits needed to support TCP and UDP packets over IPv6 and
turn the offload advertising back on.

Orabug: 26289579

Signed-off-by: Shannon Nelson <shannon.nelson@oracle.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Shannon Nelson 2017-07-06 16:57:10 -07:00 committed by David S. Miller
parent edaf382518
commit 98524e04e0
3 changed files with 81 additions and 13 deletions

View file

@ -248,7 +248,7 @@ static struct net_device *vsw_alloc_netdev(u8 hwaddr[],
dev->ethtool_ops = &vsw_ethtool_ops;
dev->watchdog_timeo = VSW_TX_TIMEOUT;
dev->hw_features = NETIF_F_IP_CSUM | NETIF_F_SG;
dev->hw_features = NETIF_F_HW_CSUM | NETIF_F_SG;
dev->features = dev->hw_features;
/* MTU range: 68 - 65535 */

View file

@ -312,7 +312,7 @@ static struct vnet *vnet_new(const u64 *local_mac,
dev->watchdog_timeo = VNET_TX_TIMEOUT;
dev->hw_features = NETIF_F_TSO | NETIF_F_GSO | NETIF_F_GSO_SOFTWARE |
NETIF_F_IP_CSUM | NETIF_F_SG;
NETIF_F_HW_CSUM | NETIF_F_SG;
dev->features = dev->hw_features;
/* MTU range: 68 - 65535 */

View file

@ -303,7 +303,7 @@ static struct sk_buff *alloc_and_align_skb(struct net_device *dev,
return skb;
}
static inline void vnet_fullcsum(struct sk_buff *skb)
static inline void vnet_fullcsum_ipv4(struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);
int offset = skb_transport_offset(skb);
@ -335,6 +335,40 @@ static inline void vnet_fullcsum(struct sk_buff *skb)
}
}
#if IS_ENABLED(CONFIG_IPV6)
static inline void vnet_fullcsum_ipv6(struct sk_buff *skb)
{
struct ipv6hdr *ip6h = ipv6_hdr(skb);
int offset = skb_transport_offset(skb);
if (skb->protocol != htons(ETH_P_IPV6))
return;
if (ip6h->nexthdr != IPPROTO_TCP &&
ip6h->nexthdr != IPPROTO_UDP)
return;
skb->ip_summed = CHECKSUM_NONE;
skb->csum_level = 1;
skb->csum = 0;
if (ip6h->nexthdr == IPPROTO_TCP) {
struct tcphdr *ptcp = tcp_hdr(skb);
ptcp->check = 0;
skb->csum = skb_checksum(skb, offset, skb->len - offset, 0);
ptcp->check = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
skb->len - offset, IPPROTO_TCP,
skb->csum);
} else if (ip6h->nexthdr == IPPROTO_UDP) {
struct udphdr *pudp = udp_hdr(skb);
pudp->check = 0;
skb->csum = skb_checksum(skb, offset, skb->len - offset, 0);
pudp->check = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
skb->len - offset, IPPROTO_UDP,
skb->csum);
}
}
#endif
static int vnet_rx_one(struct vnet_port *port, struct vio_net_desc *desc)
{
struct net_device *dev = VNET_PORT_TO_NET_DEVICE(port);
@ -394,9 +428,14 @@ static int vnet_rx_one(struct vnet_port *port, struct vio_net_desc *desc)
struct iphdr *iph = ip_hdr(skb);
int ihl = iph->ihl * 4;
skb_reset_transport_header(skb);
skb_set_transport_header(skb, ihl);
vnet_fullcsum(skb);
vnet_fullcsum_ipv4(skb);
#if IS_ENABLED(CONFIG_IPV6)
} else if (skb->protocol == htons(ETH_P_IPV6)) {
skb_set_transport_header(skb,
sizeof(struct ipv6hdr));
vnet_fullcsum_ipv6(skb);
#endif
}
}
if (dext->flags & VNET_PKT_HCK_IPV4_HDRCKSUM_OK) {
@ -1115,24 +1154,47 @@ static inline struct sk_buff *vnet_skb_shape(struct sk_buff *skb, int ncookies)
if (skb->ip_summed == CHECKSUM_PARTIAL)
start = skb_checksum_start_offset(skb);
if (start) {
struct iphdr *iph = ip_hdr(nskb);
int offset = start + nskb->csum_offset;
/* copy the headers, no csum here */
if (skb_copy_bits(skb, 0, nskb->data, start)) {
dev_kfree_skb(nskb);
dev_kfree_skb(skb);
return NULL;
}
/* copy the rest, with csum calculation */
*(__sum16 *)(skb->data + offset) = 0;
csum = skb_copy_and_csum_bits(skb, start,
nskb->data + start,
skb->len - start, 0);
if (iph->protocol == IPPROTO_TCP ||
iph->protocol == IPPROTO_UDP) {
csum = csum_tcpudp_magic(iph->saddr, iph->daddr,
skb->len - start,
iph->protocol, csum);
/* add in the header checksums */
if (skb->protocol == htons(ETH_P_IP)) {
struct iphdr *iph = ip_hdr(nskb);
if (iph->protocol == IPPROTO_TCP ||
iph->protocol == IPPROTO_UDP) {
csum = csum_tcpudp_magic(iph->saddr,
iph->daddr,
skb->len - start,
iph->protocol,
csum);
}
} else if (skb->protocol == htons(ETH_P_IPV6)) {
struct ipv6hdr *ip6h = ipv6_hdr(nskb);
if (ip6h->nexthdr == IPPROTO_TCP ||
ip6h->nexthdr == IPPROTO_UDP) {
csum = csum_ipv6_magic(&ip6h->saddr,
&ip6h->daddr,
skb->len - start,
ip6h->nexthdr,
csum);
}
}
/* save the final result */
*(__sum16 *)(nskb->data + offset) = csum;
nskb->ip_summed = CHECKSUM_NONE;
@ -1318,8 +1380,14 @@ int sunvnet_start_xmit_common(struct sk_buff *skb, struct net_device *dev,
if (unlikely(!skb))
goto out_dropped;
if (skb->ip_summed == CHECKSUM_PARTIAL)
vnet_fullcsum(skb);
if (skb->ip_summed == CHECKSUM_PARTIAL) {
if (skb->protocol == htons(ETH_P_IP))
vnet_fullcsum_ipv4(skb);
#if IS_ENABLED(CONFIG_IPV6)
else if (skb->protocol == htons(ETH_P_IPV6))
vnet_fullcsum_ipv6(skb);
#endif
}
dr = &port->vio.drings[VIO_DRIVER_TX_RING];
i = skb_get_queue_mapping(skb);