2010-08-22 10:05:39 +04:00
/*
* GRE over IPv4 demultiplexer driver
*
* Authors : Dmitry Kozlov ( xeb @ mail . ru )
*
* 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 .
*
*/
2012-03-12 11:03:32 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2010-08-22 10:05:39 +04:00
# include <linux/module.h>
2013-06-18 04:49:38 +04:00
# include <linux/if.h>
# include <linux/icmp.h>
2010-08-22 10:05:39 +04:00
# include <linux/kernel.h>
# include <linux/kmod.h>
# include <linux/skbuff.h>
# include <linux/in.h>
2011-07-23 00:49:40 +04:00
# include <linux/ip.h>
2010-08-22 10:05:39 +04:00
# include <linux/netdevice.h>
2013-02-14 18:02:41 +04:00
# include <linux/if_tunnel.h>
2010-08-22 10:05:39 +04:00
# include <linux/spinlock.h>
# include <net/protocol.h>
# include <net/gre.h>
2013-06-18 04:49:38 +04:00
# include <net/icmp.h>
# include <net/route.h>
# include <net/xfrm.h>
2010-08-22 10:05:39 +04:00
2010-10-25 01:33:16 +04:00
static const struct gre_protocol __rcu * gre_proto [ GREPROTO_MAX ] __read_mostly ;
2010-08-22 10:05:39 +04:00
int gre_add_protocol ( const struct gre_protocol * proto , u8 version )
{
if ( version > = GREPROTO_MAX )
2013-06-18 04:49:32 +04:00
return - EINVAL ;
2010-08-22 10:05:39 +04:00
2013-06-18 04:49:32 +04:00
return ( cmpxchg ( ( const struct gre_protocol * * ) & gre_proto [ version ] , NULL , proto ) = = NULL ) ?
0 : - EBUSY ;
2010-08-22 10:05:39 +04:00
}
EXPORT_SYMBOL_GPL ( gre_add_protocol ) ;
int gre_del_protocol ( const struct gre_protocol * proto , u8 version )
{
2013-06-18 04:49:32 +04:00
int ret ;
2010-08-22 10:05:39 +04:00
if ( version > = GREPROTO_MAX )
2013-06-18 04:49:32 +04:00
return - EINVAL ;
ret = ( cmpxchg ( ( const struct gre_protocol * * ) & gre_proto [ version ] , proto , NULL ) = = proto ) ?
0 : - EBUSY ;
if ( ret )
return ret ;
2010-08-22 10:05:39 +04:00
synchronize_rcu ( ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( gre_del_protocol ) ;
2016-05-03 16:00:21 +03:00
/* Fills in tpi and returns header length to be pulled. */
2016-04-30 03:12:16 +03:00
int gre_parse_header ( struct sk_buff * skb , struct tnl_ptk_info * tpi ,
2016-05-03 16:00:21 +03:00
bool * csum_err )
2016-04-30 03:12:16 +03:00
{
const struct gre_base_hdr * greh ;
__be32 * options ;
int hdr_len ;
if ( unlikely ( ! pskb_may_pull ( skb , sizeof ( struct gre_base_hdr ) ) ) )
return - EINVAL ;
greh = ( struct gre_base_hdr * ) skb_transport_header ( skb ) ;
if ( unlikely ( greh - > flags & ( GRE_VERSION | GRE_ROUTING ) ) )
return - EINVAL ;
tpi - > flags = gre_flags_to_tnl_flags ( greh - > flags ) ;
hdr_len = gre_calc_hlen ( tpi - > flags ) ;
if ( ! pskb_may_pull ( skb , hdr_len ) )
return - EINVAL ;
greh = ( struct gre_base_hdr * ) skb_transport_header ( skb ) ;
tpi - > proto = greh - > protocol ;
options = ( __be32 * ) ( greh + 1 ) ;
if ( greh - > flags & GRE_CSUM ) {
if ( skb_checksum_simple_validate ( skb ) ) {
* csum_err = true ;
return - EINVAL ;
}
skb_checksum_try_convert ( skb , IPPROTO_GRE , 0 ,
null_compute_pseudo ) ;
options + + ;
}
if ( greh - > flags & GRE_KEY ) {
tpi - > key = * options ;
options + + ;
} else {
tpi - > key = 0 ;
}
if ( unlikely ( greh - > flags & GRE_SEQ ) ) {
tpi - > seq = * options ;
options + + ;
} else {
tpi - > seq = 0 ;
}
/* WCCP version 1 and 2 protocol decoding.
* - Change protocol to IP
* - When dealing with WCCPv2 , Skip extra 4 bytes in GRE header
*/
if ( greh - > flags = = 0 & & tpi - > proto = = htons ( ETH_P_WCCP ) ) {
tpi - > proto = htons ( ETH_P_IP ) ;
2016-05-03 18:10:06 +03:00
if ( ( * ( u8 * ) options & 0xF0 ) ! = 0x40 )
2016-04-30 03:12:16 +03:00
hdr_len + = 4 ;
}
2016-05-03 16:00:21 +03:00
return hdr_len ;
2016-04-30 03:12:16 +03:00
}
EXPORT_SYMBOL ( gre_parse_header ) ;
2010-08-22 10:05:39 +04:00
static int gre_rcv ( struct sk_buff * skb )
{
const struct gre_protocol * proto ;
u8 ver ;
int ret ;
if ( ! pskb_may_pull ( skb , 12 ) )
goto drop ;
ver = skb - > data [ 1 ] & 0x7f ;
if ( ver > = GREPROTO_MAX )
goto drop ;
rcu_read_lock ( ) ;
proto = rcu_dereference ( gre_proto [ ver ] ) ;
if ( ! proto | | ! proto - > handler )
goto drop_unlock ;
ret = proto - > handler ( skb ) ;
rcu_read_unlock ( ) ;
return ret ;
drop_unlock :
rcu_read_unlock ( ) ;
drop :
kfree_skb ( skb ) ;
return NET_RX_DROP ;
}
static void gre_err ( struct sk_buff * skb , u32 info )
{
const struct gre_protocol * proto ;
2011-07-23 00:49:40 +04:00
const struct iphdr * iph = ( const struct iphdr * ) skb - > data ;
u8 ver = skb - > data [ ( iph - > ihl < < 2 ) + 1 ] & 0x7f ;
2010-08-22 10:05:39 +04:00
if ( ver > = GREPROTO_MAX )
2011-07-23 00:49:40 +04:00
return ;
2010-08-22 10:05:39 +04:00
rcu_read_lock ( ) ;
proto = rcu_dereference ( gre_proto [ ver ] ) ;
2011-07-23 00:49:40 +04:00
if ( proto & & proto - > err_handler )
proto - > err_handler ( skb , info ) ;
2010-08-22 10:05:39 +04:00
rcu_read_unlock ( ) ;
}
static const struct net_protocol net_gre_protocol = {
. handler = gre_rcv ,
. err_handler = gre_err ,
. netns_ok = 1 ,
} ;
static int __init gre_init ( void )
{
2012-03-12 11:03:32 +04:00
pr_info ( " GRE over IPv4 demultiplexor driver \n " ) ;
2010-08-22 10:05:39 +04:00
if ( inet_add_protocol ( & net_gre_protocol , IPPROTO_GRE ) < 0 ) {
2012-03-12 11:03:32 +04:00
pr_err ( " can't add protocol \n " ) ;
2015-08-08 09:51:52 +03:00
return - EAGAIN ;
2013-06-18 04:49:38 +04:00
}
2010-08-22 10:05:39 +04:00
return 0 ;
}
static void __exit gre_exit ( void )
{
inet_del_protocol ( & net_gre_protocol , IPPROTO_GRE ) ;
}
module_init ( gre_init ) ;
module_exit ( gre_exit ) ;
MODULE_DESCRIPTION ( " GRE over IPv4 demultiplexer driver " ) ;
MODULE_AUTHOR ( " D. Kozlov (xeb@mail.ru) " ) ;
MODULE_LICENSE ( " GPL " ) ;