2015-12-15 15:41:38 -08:00
# include <linux/jhash.h>
# include <linux/netfilter.h>
# include <linux/rcupdate.h>
# include <linux/rhashtable.h>
# include <linux/vmalloc.h>
# include <net/genetlink.h>
# include <net/ila.h>
# include <net/netns/generic.h>
# include <uapi/linux/genetlink.h>
# include "ila.h"
struct ila_xlat_params {
struct ila_params ip ;
int ifindex ;
} ;
struct ila_map {
2016-04-23 11:46:55 -07:00
struct ila_xlat_params xp ;
2015-12-15 15:41:38 -08:00
struct rhash_head node ;
struct ila_map __rcu * next ;
struct rcu_head rcu ;
} ;
static unsigned int ila_net_id ;
struct ila_net {
struct rhashtable rhash_table ;
spinlock_t * locks ; /* Bucket locks for entry manipulation */
unsigned int locks_mask ;
bool hooks_registered ;
} ;
# define LOCKS_PER_CPU 10
static int alloc_ila_locks ( struct ila_net * ilan )
{
unsigned int i , size ;
unsigned int nr_pcpus = num_possible_cpus ( ) ;
nr_pcpus = min_t ( unsigned int , nr_pcpus , 32UL ) ;
size = roundup_pow_of_two ( nr_pcpus * LOCKS_PER_CPU ) ;
if ( sizeof ( spinlock_t ) ! = 0 ) {
# ifdef CONFIG_NUMA
if ( size * sizeof ( spinlock_t ) > PAGE_SIZE )
ilan - > locks = vmalloc ( size * sizeof ( spinlock_t ) ) ;
else
# endif
ilan - > locks = kmalloc_array ( size , sizeof ( spinlock_t ) ,
GFP_KERNEL ) ;
if ( ! ilan - > locks )
return - ENOMEM ;
for ( i = 0 ; i < size ; i + + )
spin_lock_init ( & ilan - > locks [ i ] ) ;
}
ilan - > locks_mask = size - 1 ;
return 0 ;
}
static u32 hashrnd __read_mostly ;
static __always_inline void __ila_hash_secret_init ( void )
{
net_get_random_once ( & hashrnd , sizeof ( hashrnd ) ) ;
}
2016-04-23 11:46:56 -07:00
static inline u32 ila_locator_hash ( struct ila_locator loc )
2015-12-15 15:41:38 -08:00
{
2016-04-23 11:46:56 -07:00
u32 * v = ( u32 * ) loc . v32 ;
2015-12-15 15:41:38 -08:00
return jhash_2words ( v [ 0 ] , v [ 1 ] , hashrnd ) ;
}
2016-04-23 11:46:55 -07:00
static inline spinlock_t * ila_get_lock ( struct ila_net * ilan ,
2016-04-23 11:46:56 -07:00
struct ila_locator loc )
2015-12-15 15:41:38 -08:00
{
2016-04-23 11:46:56 -07:00
return & ilan - > locks [ ila_locator_hash ( loc ) & ilan - > locks_mask ] ;
2015-12-15 15:41:38 -08:00
}
2016-04-23 11:46:55 -07:00
static inline int ila_cmp_wildcards ( struct ila_map * ila ,
2016-04-23 11:46:56 -07:00
struct ila_addr * iaddr , int ifindex )
2015-12-15 15:41:38 -08:00
{
2016-04-23 11:46:56 -07:00
return ( ila - > xp . ifindex & & ila - > xp . ifindex ! = ifindex ) ;
2015-12-15 15:41:38 -08:00
}
2016-04-23 11:46:55 -07:00
static inline int ila_cmp_params ( struct ila_map * ila ,
struct ila_xlat_params * xp )
2015-12-15 15:41:38 -08:00
{
2016-04-23 11:46:56 -07:00
return ( ila - > xp . ifindex ! = xp - > ifindex ) ;
2015-12-15 15:41:38 -08:00
}
static int ila_cmpfn ( struct rhashtable_compare_arg * arg ,
const void * obj )
{
const struct ila_map * ila = obj ;
2016-04-23 11:46:56 -07:00
return ( ila - > xp . ip . locator_match . v64 ! = * ( __be64 * ) arg - > key ) ;
2015-12-15 15:41:38 -08:00
}
static inline int ila_order ( struct ila_map * ila )
{
int score = 0 ;
2016-04-23 11:46:55 -07:00
if ( ila - > xp . ifindex )
2015-12-15 15:41:38 -08:00
score + = 1 < < 1 ;
return score ;
}
static const struct rhashtable_params rht_params = {
. nelem_hint = 1024 ,
. head_offset = offsetof ( struct ila_map , node ) ,
2016-04-23 11:46:56 -07:00
. key_offset = offsetof ( struct ila_map , xp . ip . locator_match ) ,
2015-12-15 15:41:38 -08:00
. key_len = sizeof ( u64 ) , /* identifier */
. max_size = 1048576 ,
. min_size = 256 ,
. automatic_shrinking = true ,
. obj_cmpfn = ila_cmpfn ,
} ;
static struct genl_family ila_nl_family = {
. id = GENL_ID_GENERATE ,
. hdrsize = 0 ,
. name = ILA_GENL_NAME ,
. version = ILA_GENL_VERSION ,
. maxattr = ILA_ATTR_MAX ,
. netnsok = true ,
. parallel_ops = true ,
} ;
2016-08-31 15:20:51 -07:00
static const struct nla_policy ila_nl_policy [ ILA_ATTR_MAX + 1 ] = {
2015-12-15 15:41:38 -08:00
[ ILA_ATTR_LOCATOR ] = { . type = NLA_U64 , } ,
[ ILA_ATTR_LOCATOR_MATCH ] = { . type = NLA_U64 , } ,
[ ILA_ATTR_IFINDEX ] = { . type = NLA_U32 , } ,
2016-04-23 11:46:57 -07:00
[ ILA_ATTR_CSUM_MODE ] = { . type = NLA_U8 , } ,
2015-12-15 15:41:38 -08:00
} ;
static int parse_nl_config ( struct genl_info * info ,
2016-04-23 11:46:55 -07:00
struct ila_xlat_params * xp )
2015-12-15 15:41:38 -08:00
{
2016-04-23 11:46:55 -07:00
memset ( xp , 0 , sizeof ( * xp ) ) ;
2015-12-15 15:41:38 -08:00
if ( info - > attrs [ ILA_ATTR_LOCATOR ] )
2016-04-23 11:46:55 -07:00
xp - > ip . locator . v64 = ( __force __be64 ) nla_get_u64 (
2015-12-15 15:41:38 -08:00
info - > attrs [ ILA_ATTR_LOCATOR ] ) ;
if ( info - > attrs [ ILA_ATTR_LOCATOR_MATCH ] )
2016-04-23 11:46:55 -07:00
xp - > ip . locator_match . v64 = ( __force __be64 ) nla_get_u64 (
2015-12-15 15:41:38 -08:00
info - > attrs [ ILA_ATTR_LOCATOR_MATCH ] ) ;
2016-04-23 11:46:57 -07:00
if ( info - > attrs [ ILA_ATTR_CSUM_MODE ] )
xp - > ip . csum_mode = nla_get_u8 ( info - > attrs [ ILA_ATTR_CSUM_MODE ] ) ;
2015-12-15 15:41:38 -08:00
if ( info - > attrs [ ILA_ATTR_IFINDEX ] )
2016-04-23 11:46:55 -07:00
xp - > ifindex = nla_get_s32 ( info - > attrs [ ILA_ATTR_IFINDEX ] ) ;
2015-12-15 15:41:38 -08:00
return 0 ;
}
/* Must be called with rcu readlock */
2016-04-23 11:46:55 -07:00
static inline struct ila_map * ila_lookup_wildcards ( struct ila_addr * iaddr ,
2015-12-15 15:41:38 -08:00
int ifindex ,
struct ila_net * ilan )
{
struct ila_map * ila ;
2016-04-23 11:46:56 -07:00
ila = rhashtable_lookup_fast ( & ilan - > rhash_table , & iaddr - > loc ,
2016-04-23 11:46:55 -07:00
rht_params ) ;
2015-12-15 15:41:38 -08:00
while ( ila ) {
2016-04-23 11:46:56 -07:00
if ( ! ila_cmp_wildcards ( ila , iaddr , ifindex ) )
2015-12-15 15:41:38 -08:00
return ila ;
ila = rcu_access_pointer ( ila - > next ) ;
}
return NULL ;
}
/* Must be called with rcu readlock */
2016-04-23 11:46:55 -07:00
static inline struct ila_map * ila_lookup_by_params ( struct ila_xlat_params * xp ,
2015-12-15 15:41:38 -08:00
struct ila_net * ilan )
{
struct ila_map * ila ;
2016-04-23 11:46:56 -07:00
ila = rhashtable_lookup_fast ( & ilan - > rhash_table ,
& xp - > ip . locator_match ,
2015-12-15 15:41:38 -08:00
rht_params ) ;
while ( ila ) {
2016-04-23 11:46:55 -07:00
if ( ! ila_cmp_params ( ila , xp ) )
2015-12-15 15:41:38 -08:00
return ila ;
ila = rcu_access_pointer ( ila - > next ) ;
}
return NULL ;
}
static inline void ila_release ( struct ila_map * ila )
{
kfree_rcu ( ila , rcu ) ;
}
static void ila_free_cb ( void * ptr , void * arg )
{
struct ila_map * ila = ( struct ila_map * ) ptr , * next ;
/* Assume rcu_readlock held */
while ( ila ) {
next = rcu_access_pointer ( ila - > next ) ;
ila_release ( ila ) ;
ila = next ;
}
}
2016-06-07 16:09:44 -07:00
static int ila_xlat_addr ( struct sk_buff * skb , bool set_csum_neutral ) ;
2015-12-15 15:41:38 -08:00
static unsigned int
ila_nf_input ( void * priv ,
struct sk_buff * skb ,
const struct nf_hook_state * state )
{
2016-06-07 16:09:44 -07:00
ila_xlat_addr ( skb , false ) ;
2015-12-15 15:41:38 -08:00
return NF_ACCEPT ;
}
static struct nf_hook_ops ila_nf_hook_ops [ ] __read_mostly = {
{
. hook = ila_nf_input ,
. pf = NFPROTO_IPV6 ,
. hooknum = NF_INET_PRE_ROUTING ,
. priority = - 1 ,
} ,
} ;
2016-04-23 11:46:55 -07:00
static int ila_add_mapping ( struct net * net , struct ila_xlat_params * xp )
2015-12-15 15:41:38 -08:00
{
struct ila_net * ilan = net_generic ( net , ila_net_id ) ;
struct ila_map * ila , * head ;
2016-04-23 11:46:56 -07:00
spinlock_t * lock = ila_get_lock ( ilan , xp - > ip . locator_match ) ;
2015-12-15 15:41:38 -08:00
int err = 0 , order ;
if ( ! ilan - > hooks_registered ) {
/* We defer registering net hooks in the namespace until the
* first mapping is added .
*/
err = nf_register_net_hooks ( net , ila_nf_hook_ops ,
ARRAY_SIZE ( ila_nf_hook_ops ) ) ;
if ( err )
return err ;
ilan - > hooks_registered = true ;
}
ila = kzalloc ( sizeof ( * ila ) , GFP_KERNEL ) ;
if ( ! ila )
return - ENOMEM ;
2016-04-23 11:46:57 -07:00
ila_init_saved_csum ( & xp - > ip ) ;
2015-12-15 15:41:38 -08:00
2016-04-23 11:46:57 -07:00
ila - > xp = * xp ;
2015-12-15 15:41:38 -08:00
order = ila_order ( ila ) ;
spin_lock ( lock ) ;
2016-04-23 11:46:56 -07:00
head = rhashtable_lookup_fast ( & ilan - > rhash_table ,
& xp - > ip . locator_match ,
2015-12-15 15:41:38 -08:00
rht_params ) ;
if ( ! head ) {
/* New entry for the rhash_table */
err = rhashtable_lookup_insert_fast ( & ilan - > rhash_table ,
& ila - > node , rht_params ) ;
} else {
struct ila_map * tila = head , * prev = NULL ;
do {
2016-04-23 11:46:55 -07:00
if ( ! ila_cmp_params ( tila , xp ) ) {
2015-12-15 15:41:38 -08:00
err = - EEXIST ;
goto out ;
}
if ( order > ila_order ( tila ) )
break ;
prev = tila ;
tila = rcu_dereference_protected ( tila - > next ,
lockdep_is_held ( lock ) ) ;
} while ( tila ) ;
if ( prev ) {
/* Insert in sub list of head */
RCU_INIT_POINTER ( ila - > next , tila ) ;
rcu_assign_pointer ( prev - > next , ila ) ;
} else {
/* Make this ila new head */
RCU_INIT_POINTER ( ila - > next , head ) ;
err = rhashtable_replace_fast ( & ilan - > rhash_table ,
& head - > node ,
& ila - > node , rht_params ) ;
if ( err )
goto out ;
}
}
out :
spin_unlock ( lock ) ;
if ( err )
kfree ( ila ) ;
return err ;
}
2016-04-23 11:46:55 -07:00
static int ila_del_mapping ( struct net * net , struct ila_xlat_params * xp )
2015-12-15 15:41:38 -08:00
{
struct ila_net * ilan = net_generic ( net , ila_net_id ) ;
struct ila_map * ila , * head , * prev ;
2016-04-23 11:46:56 -07:00
spinlock_t * lock = ila_get_lock ( ilan , xp - > ip . locator_match ) ;
2015-12-15 15:41:38 -08:00
int err = - ENOENT ;
spin_lock ( lock ) ;
head = rhashtable_lookup_fast ( & ilan - > rhash_table ,
2016-04-23 11:46:56 -07:00
& xp - > ip . locator_match , rht_params ) ;
2015-12-15 15:41:38 -08:00
ila = head ;
prev = NULL ;
while ( ila ) {
2016-04-23 11:46:55 -07:00
if ( ila_cmp_params ( ila , xp ) ) {
2015-12-15 15:41:38 -08:00
prev = ila ;
ila = rcu_dereference_protected ( ila - > next ,
lockdep_is_held ( lock ) ) ;
continue ;
}
err = 0 ;
if ( prev ) {
/* Not head, just delete from list */
rcu_assign_pointer ( prev - > next , ila - > next ) ;
} else {
/* It is the head. If there is something in the
* sublist we need to make a new head .
*/
head = rcu_dereference_protected ( ila - > next ,
lockdep_is_held ( lock ) ) ;
if ( head ) {
/* Put first entry in the sublist into the
* table
*/
err = rhashtable_replace_fast (
& ilan - > rhash_table , & ila - > node ,
& head - > node , rht_params ) ;
if ( err )
goto out ;
} else {
/* Entry no longer used */
err = rhashtable_remove_fast ( & ilan - > rhash_table ,
& ila - > node ,
rht_params ) ;
}
}
ila_release ( ila ) ;
break ;
}
out :
spin_unlock ( lock ) ;
return err ;
}
static int ila_nl_cmd_add_mapping ( struct sk_buff * skb , struct genl_info * info )
{
struct net * net = genl_info_net ( info ) ;
struct ila_xlat_params p ;
int err ;
err = parse_nl_config ( info , & p ) ;
if ( err )
return err ;
return ila_add_mapping ( net , & p ) ;
}
static int ila_nl_cmd_del_mapping ( struct sk_buff * skb , struct genl_info * info )
{
struct net * net = genl_info_net ( info ) ;
2016-04-23 11:46:55 -07:00
struct ila_xlat_params xp ;
2015-12-15 15:41:38 -08:00
int err ;
2016-04-23 11:46:55 -07:00
err = parse_nl_config ( info , & xp ) ;
2015-12-15 15:41:38 -08:00
if ( err )
return err ;
2016-04-23 11:46:55 -07:00
ila_del_mapping ( net , & xp ) ;
2015-12-15 15:41:38 -08:00
return 0 ;
}
static int ila_fill_info ( struct ila_map * ila , struct sk_buff * msg )
{
2016-04-23 11:46:56 -07:00
if ( nla_put_u64_64bit ( msg , ILA_ATTR_LOCATOR ,
2016-04-23 11:46:55 -07:00
( __force u64 ) ila - > xp . ip . locator . v64 ,
2016-04-25 10:25:16 +02:00
ILA_ATTR_PAD ) | |
nla_put_u64_64bit ( msg , ILA_ATTR_LOCATOR_MATCH ,
2016-04-23 11:46:55 -07:00
( __force u64 ) ila - > xp . ip . locator_match . v64 ,
2016-04-25 10:25:16 +02:00
ILA_ATTR_PAD ) | |
2016-04-23 11:46:57 -07:00
nla_put_s32 ( msg , ILA_ATTR_IFINDEX , ila - > xp . ifindex ) | |
nla_put_u32 ( msg , ILA_ATTR_CSUM_MODE , ila - > xp . ip . csum_mode ) )
2015-12-15 15:41:38 -08:00
return - 1 ;
return 0 ;
}
static int ila_dump_info ( struct ila_map * ila ,
u32 portid , u32 seq , u32 flags ,
struct sk_buff * skb , u8 cmd )
{
void * hdr ;
hdr = genlmsg_put ( skb , portid , seq , & ila_nl_family , flags , cmd ) ;
if ( ! hdr )
return - ENOMEM ;
if ( ila_fill_info ( ila , skb ) < 0 )
goto nla_put_failure ;
genlmsg_end ( skb , hdr ) ;
return 0 ;
nla_put_failure :
genlmsg_cancel ( skb , hdr ) ;
return - EMSGSIZE ;
}
static int ila_nl_cmd_get_mapping ( struct sk_buff * skb , struct genl_info * info )
{
struct net * net = genl_info_net ( info ) ;
struct ila_net * ilan = net_generic ( net , ila_net_id ) ;
struct sk_buff * msg ;
2016-04-23 11:46:55 -07:00
struct ila_xlat_params xp ;
2015-12-15 15:41:38 -08:00
struct ila_map * ila ;
int ret ;
2016-04-23 11:46:55 -07:00
ret = parse_nl_config ( info , & xp ) ;
2015-12-15 15:41:38 -08:00
if ( ret )
return ret ;
msg = nlmsg_new ( NLMSG_DEFAULT_SIZE , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
rcu_read_lock ( ) ;
2016-04-23 11:46:55 -07:00
ila = ila_lookup_by_params ( & xp , ilan ) ;
2015-12-15 15:41:38 -08:00
if ( ila ) {
ret = ila_dump_info ( ila ,
info - > snd_portid ,
info - > snd_seq , 0 , msg ,
info - > genlhdr - > cmd ) ;
}
rcu_read_unlock ( ) ;
if ( ret < 0 )
goto out_free ;
return genlmsg_reply ( msg , info ) ;
out_free :
nlmsg_free ( msg ) ;
return ret ;
}
struct ila_dump_iter {
struct rhashtable_iter rhiter ;
} ;
static int ila_nl_dump_start ( struct netlink_callback * cb )
{
struct net * net = sock_net ( cb - > skb - > sk ) ;
struct ila_net * ilan = net_generic ( net , ila_net_id ) ;
struct ila_dump_iter * iter = ( struct ila_dump_iter * ) cb - > args ;
2016-03-02 10:09:19 -05:00
return rhashtable_walk_init ( & ilan - > rhash_table , & iter - > rhiter ,
GFP_KERNEL ) ;
2015-12-15 15:41:38 -08:00
}
static int ila_nl_dump_done ( struct netlink_callback * cb )
{
struct ila_dump_iter * iter = ( struct ila_dump_iter * ) cb - > args ;
rhashtable_walk_exit ( & iter - > rhiter ) ;
return 0 ;
}
static int ila_nl_dump ( struct sk_buff * skb , struct netlink_callback * cb )
{
struct ila_dump_iter * iter = ( struct ila_dump_iter * ) cb - > args ;
struct rhashtable_iter * rhiter = & iter - > rhiter ;
struct ila_map * ila ;
int ret ;
ret = rhashtable_walk_start ( rhiter ) ;
if ( ret & & ret ! = - EAGAIN )
goto done ;
for ( ; ; ) {
ila = rhashtable_walk_next ( rhiter ) ;
if ( IS_ERR ( ila ) ) {
if ( PTR_ERR ( ila ) = = - EAGAIN )
continue ;
ret = PTR_ERR ( ila ) ;
goto done ;
} else if ( ! ila ) {
break ;
}
while ( ila ) {
ret = ila_dump_info ( ila , NETLINK_CB ( cb - > skb ) . portid ,
cb - > nlh - > nlmsg_seq , NLM_F_MULTI ,
skb , ILA_CMD_GET ) ;
if ( ret )
goto done ;
ila = rcu_access_pointer ( ila - > next ) ;
}
}
ret = skb - > len ;
done :
rhashtable_walk_stop ( rhiter ) ;
return ret ;
}
static const struct genl_ops ila_nl_ops [ ] = {
{
. cmd = ILA_CMD_ADD ,
. doit = ila_nl_cmd_add_mapping ,
. policy = ila_nl_policy ,
. flags = GENL_ADMIN_PERM ,
} ,
{
. cmd = ILA_CMD_DEL ,
. doit = ila_nl_cmd_del_mapping ,
. policy = ila_nl_policy ,
. flags = GENL_ADMIN_PERM ,
} ,
{
. cmd = ILA_CMD_GET ,
. doit = ila_nl_cmd_get_mapping ,
. start = ila_nl_dump_start ,
. dumpit = ila_nl_dump ,
. done = ila_nl_dump_done ,
. policy = ila_nl_policy ,
} ,
} ;
# define ILA_HASH_TABLE_SIZE 1024
static __net_init int ila_init_net ( struct net * net )
{
int err ;
struct ila_net * ilan = net_generic ( net , ila_net_id ) ;
err = alloc_ila_locks ( ilan ) ;
if ( err )
return err ;
rhashtable_init ( & ilan - > rhash_table , & rht_params ) ;
return 0 ;
}
static __net_exit void ila_exit_net ( struct net * net )
{
struct ila_net * ilan = net_generic ( net , ila_net_id ) ;
rhashtable_free_and_destroy ( & ilan - > rhash_table , ila_free_cb , NULL ) ;
kvfree ( ilan - > locks ) ;
if ( ilan - > hooks_registered )
nf_unregister_net_hooks ( net , ila_nf_hook_ops ,
ARRAY_SIZE ( ila_nf_hook_ops ) ) ;
}
static struct pernet_operations ila_net_ops = {
. init = ila_init_net ,
. exit = ila_exit_net ,
. id = & ila_net_id ,
. size = sizeof ( struct ila_net ) ,
} ;
2016-06-07 16:09:44 -07:00
static int ila_xlat_addr ( struct sk_buff * skb , bool set_csum_neutral )
2015-12-15 15:41:38 -08:00
{
struct ila_map * ila ;
struct ipv6hdr * ip6h = ipv6_hdr ( skb ) ;
struct net * net = dev_net ( skb - > dev ) ;
struct ila_net * ilan = net_generic ( net , ila_net_id ) ;
2016-04-23 11:46:55 -07:00
struct ila_addr * iaddr = ila_a2i ( & ip6h - > daddr ) ;
2015-12-15 15:41:38 -08:00
/* Assumes skb contains a valid IPv6 header that is pulled */
2016-04-23 11:46:56 -07:00
if ( ! ila_addr_is_ila ( iaddr ) ) {
/* Type indicates this is not an ILA address */
return 0 ;
}
2015-12-15 15:41:38 -08:00
rcu_read_lock ( ) ;
2016-04-23 11:46:56 -07:00
ila = ila_lookup_wildcards ( iaddr , skb - > dev - > ifindex , ilan ) ;
2015-12-15 15:41:38 -08:00
if ( ila )
2016-06-07 16:09:44 -07:00
ila_update_ipv6_locator ( skb , & ila - > xp . ip , set_csum_neutral ) ;
2015-12-15 15:41:38 -08:00
rcu_read_unlock ( ) ;
return 0 ;
}
int ila_xlat_init ( void )
{
int ret ;
ret = register_pernet_device ( & ila_net_ops ) ;
if ( ret )
goto exit ;
ret = genl_register_family_with_ops ( & ila_nl_family ,
ila_nl_ops ) ;
if ( ret < 0 )
goto unregister ;
return 0 ;
unregister :
unregister_pernet_device ( & ila_net_ops ) ;
exit :
return ret ;
}
void ila_xlat_fini ( void )
{
genl_unregister_family ( & ila_nl_family ) ;
unregister_pernet_device ( & ila_net_ops ) ;
}