2012-11-15 08:49:18 +00:00
/*
* IPV6 GSO / GRO offload support
* Linux INET6 implementation
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*
* UDPv6 GSO support
*/
# include <linux/skbuff.h>
2014-08-22 13:34:44 -07:00
# include <linux/netdevice.h>
2012-11-15 08:49:18 +00:00
# include <net/protocol.h>
# include <net/ipv6.h>
# include <net/udp.h>
2012-11-15 16:35:37 +00:00
# include <net/ip6_checksum.h>
2012-11-15 08:49:18 +00:00
# include "ip6_offload.h"
static struct sk_buff * udp6_ufo_fragment ( struct sk_buff * skb ,
2013-08-31 13:44:37 +08:00
netdev_features_t features )
2012-11-15 08:49:18 +00:00
{
struct sk_buff * segs = ERR_PTR ( - EINVAL ) ;
unsigned int mss ;
unsigned int unfrag_ip6hlen , unfrag_len ;
struct frag_hdr * fptr ;
2013-05-30 06:45:27 +00:00
u8 * packet_start , * prevhdr ;
2012-11-15 08:49:18 +00:00
u8 nexthdr ;
u8 frag_hdr_sz = sizeof ( struct frag_hdr ) ;
__wsum csum ;
2013-05-30 06:45:27 +00:00
int tnl_hlen ;
2012-11-15 08:49:18 +00:00
mss = skb_shinfo ( skb ) - > gso_size ;
if ( unlikely ( skb - > len < = mss ) )
goto out ;
if ( skb_gso_ok ( skb , features | NETIF_F_GSO_ROBUST ) ) {
/* Packet is from an untrusted source, reset gso_segs. */
int type = skb_shinfo ( skb ) - > gso_type ;
2013-03-07 13:21:51 +00:00
if ( unlikely ( type & ~ ( SKB_GSO_UDP |
SKB_GSO_DODGY |
SKB_GSO_UDP_TUNNEL |
2014-06-04 17:20:16 -07:00
SKB_GSO_UDP_TUNNEL_CSUM |
2014-11-04 09:06:54 -08:00
SKB_GSO_TUNNEL_REMCSUM |
2013-05-23 21:02:52 +00:00
SKB_GSO_GRE |
2014-06-04 17:20:23 -07:00
SKB_GSO_GRE_CSUM |
2013-10-19 11:42:57 -07:00
SKB_GSO_IPIP |
2014-11-05 15:27:48 -08:00
SKB_GSO_SIT ) | |
2012-11-15 08:49:18 +00:00
! ( type & ( SKB_GSO_UDP ) ) ) )
goto out ;
skb_shinfo ( skb ) - > gso_segs = DIV_ROUND_UP ( skb - > len , mss ) ;
2015-02-03 16:36:15 -05:00
/* Set the IPv6 fragment id if not set yet */
if ( ! skb_shinfo ( skb ) - > ip6_frag_id )
2015-03-25 17:07:45 +01:00
ipv6_proxy_select_ident ( dev_net ( skb - > dev ) , skb ) ;
2015-02-03 16:36:15 -05:00
2012-11-15 08:49:18 +00:00
segs = NULL ;
goto out ;
}
2014-06-04 17:20:16 -07:00
if ( skb - > encapsulation & & skb_shinfo ( skb ) - > gso_type &
( SKB_GSO_UDP_TUNNEL | SKB_GSO_UDP_TUNNEL_CSUM ) )
2014-09-29 20:22:29 -07:00
segs = skb_udp_tunnel_segment ( skb , features , true ) ;
2013-08-31 13:44:37 +08:00
else {
2014-09-20 14:52:29 -07:00
const struct ipv6hdr * ipv6h ;
struct udphdr * uh ;
if ( ! pskb_may_pull ( skb , sizeof ( struct udphdr ) ) )
goto out ;
2013-08-31 13:44:37 +08:00
/* Do software UFO. Complete and fill in the UDP checksum as HW cannot
* do checksum of UDP packets sent as multiple IP fragments .
*/
2014-09-20 14:52:29 -07:00
uh = udp_hdr ( skb ) ;
ipv6h = ipv6_hdr ( skb ) ;
uh - > check = 0 ;
csum = skb_checksum ( skb , 0 , skb - > len , 0 ) ;
uh - > check = udp_v6_check ( skb - > len , & ipv6h - > saddr ,
& ipv6h - > daddr , csum ) ;
if ( uh - > check = = 0 )
uh - > check = CSUM_MANGLED_0 ;
2013-08-31 13:44:37 +08:00
skb - > ip_summed = CHECKSUM_NONE ;
/* Check if there is enough headroom to insert fragment header. */
tnl_hlen = skb_tnl_header_len ( skb ) ;
2013-11-05 02:41:27 +01:00
if ( skb - > mac_header < ( tnl_hlen + frag_hdr_sz ) ) {
2013-08-31 13:44:37 +08:00
if ( gso_pskb_expand_head ( skb , tnl_hlen + frag_hdr_sz ) )
goto out ;
}
/* Find the unfragmentable header and shift it left by frag_hdr_sz
* bytes to insert fragment header .
*/
unfrag_ip6hlen = ip6_find_1stfragopt ( skb , & prevhdr ) ;
nexthdr = * prevhdr ;
* prevhdr = NEXTHDR_FRAGMENT ;
unfrag_len = ( skb_network_header ( skb ) - skb_mac_header ( skb ) ) +
unfrag_ip6hlen + tnl_hlen ;
packet_start = ( u8 * ) skb - > head + SKB_GSO_CB ( skb ) - > mac_offset ;
memmove ( packet_start - frag_hdr_sz , packet_start , unfrag_len ) ;
SKB_GSO_CB ( skb ) - > mac_offset - = frag_hdr_sz ;
skb - > mac_header - = frag_hdr_sz ;
skb - > network_header - = frag_hdr_sz ;
fptr = ( struct frag_hdr * ) ( skb_network_header ( skb ) + unfrag_ip6hlen ) ;
fptr - > nexthdr = nexthdr ;
fptr - > reserved = 0 ;
2015-03-19 11:22:32 +01:00
if ( ! skb_shinfo ( skb ) - > ip6_frag_id )
2015-03-25 17:07:45 +01:00
ipv6_proxy_select_ident ( dev_net ( skb - > dev ) , skb ) ;
2015-03-19 11:22:32 +01:00
fptr - > identification = skb_shinfo ( skb ) - > ip6_frag_id ;
2013-08-31 13:44:37 +08:00
/* Fragment the skb. ipv6 header and the remaining fields of the
* fragment header are updated in ipv6_gso_segment ( )
*/
segs = skb_segment ( skb , features ) ;
2013-05-30 06:45:27 +00:00
}
2012-11-15 08:49:18 +00:00
out :
return segs ;
}
2014-08-22 13:34:44 -07:00
static struct sk_buff * * udp6_gro_receive ( struct sk_buff * * head ,
struct sk_buff * skb )
{
struct udphdr * uh = udp_gro_udphdr ( skb ) ;
2014-08-31 15:12:43 -07:00
if ( unlikely ( ! uh ) )
goto flush ;
2014-08-22 13:34:44 -07:00
/* Don't bother verifying checksum if we're going to flush anyway. */
2014-09-10 21:23:18 -05:00
if ( NAPI_GRO_CB ( skb ) - > flush )
2014-08-31 15:12:43 -07:00
goto skip ;
2014-08-22 13:34:44 -07:00
2014-08-31 15:12:43 -07:00
if ( skb_gro_checksum_validate_zero_check ( skb , IPPROTO_UDP , uh - > check ,
ip6_gro_compute_pseudo ) )
goto flush ;
else if ( uh - > check )
skb_gro_checksum_try_convert ( skb , IPPROTO_UDP , uh - > check ,
ip6_gro_compute_pseudo ) ;
skip :
2014-10-03 15:48:08 -07:00
NAPI_GRO_CB ( skb ) - > is_ipv6 = 1 ;
2014-08-22 13:34:44 -07:00
return udp_gro_receive ( head , skb , uh ) ;
2014-08-31 15:12:43 -07:00
flush :
NAPI_GRO_CB ( skb ) - > flush = 1 ;
return NULL ;
2014-08-22 13:34:44 -07:00
}
2014-09-09 08:16:17 -07:00
static int udp6_gro_complete ( struct sk_buff * skb , int nhoff )
2014-08-22 13:34:44 -07:00
{
const struct ipv6hdr * ipv6h = ipv6_hdr ( skb ) ;
struct udphdr * uh = ( struct udphdr * ) ( skb - > data + nhoff ) ;
2015-02-10 16:30:29 -08:00
if ( uh - > check ) {
skb_shinfo ( skb ) - > gso_type | = SKB_GSO_UDP_TUNNEL_CSUM ;
2014-08-22 13:34:44 -07:00
uh - > check = ~ udp_v6_check ( skb - > len - nhoff , & ipv6h - > saddr ,
& ipv6h - > daddr , 0 ) ;
2015-02-10 16:30:29 -08:00
} else {
skb_shinfo ( skb ) - > gso_type | = SKB_GSO_UDP_TUNNEL ;
}
2014-08-22 13:34:44 -07:00
return udp_gro_complete ( skb , nhoff ) ;
}
2012-11-15 08:49:18 +00:00
static const struct net_offload udpv6_offload = {
2012-11-15 08:49:23 +00:00
. callbacks = {
. gso_segment = udp6_ufo_fragment ,
2014-08-22 13:34:44 -07:00
. gro_receive = udp6_gro_receive ,
. gro_complete = udp6_gro_complete ,
2012-11-15 08:49:23 +00:00
} ,
2012-11-15 08:49:18 +00:00
} ;
int __init udp_offload_init ( void )
{
return inet6_add_offload ( & udpv6_offload , IPPROTO_UDP ) ;
}