2012-11-15 12:49:18 +04: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>
# include <net/protocol.h>
# include <net/ipv6.h>
# include <net/udp.h>
2012-11-15 20:35:37 +04:00
# include <net/ip6_checksum.h>
2012-11-15 12:49:18 +04:00
# include "ip6_offload.h"
static int udp6_ufo_send_check ( struct sk_buff * skb )
{
const struct ipv6hdr * ipv6h ;
struct udphdr * uh ;
if ( ! pskb_may_pull ( skb , sizeof ( * uh ) ) )
return - EINVAL ;
2013-08-31 09:44:37 +04:00
if ( likely ( ! skb - > encapsulation ) ) {
ipv6h = ipv6_hdr ( skb ) ;
uh = udp_hdr ( skb ) ;
uh - > check = ~ csum_ipv6_magic ( & ipv6h - > saddr , & ipv6h - > daddr , skb - > len ,
IPPROTO_UDP , 0 ) ;
skb - > csum_start = skb_transport_header ( skb ) - skb - > head ;
skb - > csum_offset = offsetof ( struct udphdr , check ) ;
skb - > ip_summed = CHECKSUM_PARTIAL ;
}
2012-11-15 12:49:18 +04:00
return 0 ;
}
static struct sk_buff * udp6_ufo_fragment ( struct sk_buff * skb ,
2013-08-31 09:44:37 +04:00
netdev_features_t features )
2012-11-15 12:49:18 +04:00
{
struct sk_buff * segs = ERR_PTR ( - EINVAL ) ;
unsigned int mss ;
unsigned int unfrag_ip6hlen , unfrag_len ;
struct frag_hdr * fptr ;
2013-05-30 10:45:27 +04:00
u8 * packet_start , * prevhdr ;
2012-11-15 12:49:18 +04:00
u8 nexthdr ;
u8 frag_hdr_sz = sizeof ( struct frag_hdr ) ;
int offset ;
__wsum csum ;
2013-05-30 10:45:27 +04:00
int tnl_hlen ;
2012-11-15 12:49:18 +04: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 17:21:51 +04:00
if ( unlikely ( type & ~ ( SKB_GSO_UDP |
SKB_GSO_DODGY |
SKB_GSO_UDP_TUNNEL |
2013-05-24 01:02:52 +04:00
SKB_GSO_GRE |
2013-10-19 22:42:57 +04:00
SKB_GSO_IPIP |
2013-10-21 07:47:30 +04:00
SKB_GSO_SIT |
2013-05-24 01:02:52 +04:00
SKB_GSO_MPLS ) | |
2012-11-15 12:49:18 +04:00
! ( type & ( SKB_GSO_UDP ) ) ) )
goto out ;
skb_shinfo ( skb ) - > gso_segs = DIV_ROUND_UP ( skb - > len , mss ) ;
segs = NULL ;
goto out ;
}
2013-08-31 09:44:37 +04:00
if ( skb - > encapsulation & & skb_shinfo ( skb ) - > gso_type & SKB_GSO_UDP_TUNNEL )
2013-08-31 09:44:38 +04:00
segs = skb_udp_tunnel_segment ( skb , features ) ;
2013-08-31 09:44:37 +04:00
else {
/* Do software UFO. Complete and fill in the UDP checksum as HW cannot
* do checksum of UDP packets sent as multiple IP fragments .
*/
offset = skb_checksum_start_offset ( skb ) ;
csum = skb_checksum ( skb , offset , skb - > len - offset , 0 ) ;
offset + = skb - > csum_offset ;
* ( __sum16 * ) ( skb - > data + offset ) = csum_fold ( csum ) ;
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 05:41:27 +04:00
if ( skb - > mac_header < ( tnl_hlen + frag_hdr_sz ) ) {
2013-08-31 09:44:37 +04: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 ;
ipv6_select_ident ( fptr , ( struct rt6_info * ) skb_dst ( skb ) ) ;
/* 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 10:45:27 +04:00
}
2012-11-15 12:49:18 +04:00
out :
return segs ;
}
static const struct net_offload udpv6_offload = {
2012-11-15 12:49:23 +04:00
. callbacks = {
. gso_send_check = udp6_ufo_send_check ,
. gso_segment = udp6_ufo_fragment ,
} ,
2012-11-15 12:49:18 +04:00
} ;
int __init udp_offload_init ( void )
{
return inet6_add_offload ( & udpv6_offload , IPPROTO_UDP ) ;
}