2007-11-14 09:56:23 +03:00
/*
* IPv6 Address Label subsystem
* for the IPv6 " Default " Source Address Selection
*
* Copyright ( C ) 2007 USAGI / WIDE Project
*/
/*
* Author :
* YOSHIFUJI Hideaki @ USAGI / WIDE Project < yoshfuji @ linux - ipv6 . org >
*/
# include <linux/kernel.h>
# include <linux/list.h>
# include <linux/rcupdate.h>
# include <linux/in6.h>
# include <net/addrconf.h>
# include <linux/if_addrlabel.h>
# include <linux/netlink.h>
# include <linux/rtnetlink.h>
#if 0
# define ADDRLABEL(x...) printk(x)
# else
# define ADDRLABEL(x...) do { ; } while(0)
# endif
/*
* Policy Table
*/
struct ip6addrlbl_entry
{
struct in6_addr prefix ;
int prefixlen ;
int ifindex ;
int addrtype ;
u32 label ;
struct hlist_node list ;
atomic_t refcnt ;
struct rcu_head rcu ;
} ;
static struct ip6addrlbl_table
{
struct hlist_head head ;
spinlock_t lock ;
u32 seq ;
} ip6addrlbl_table ;
/*
* Default policy table ( RFC3484 + extensions )
*
* prefix addr_type label
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* : : 1 / 128 LOOPBACK 0
* : : / 0 N / A 1
* 2002 : : / 16 N / A 2
* : : / 96 COMPATv4 3
* : : ffff : 0 : 0 / 96 V4MAPPED 4
* fc00 : : / 7 N / A 5 ULA ( RFC 4193 )
* 2001 : : / 32 N / A 6 Teredo ( RFC 4380 )
*
* Note : 0xffffffff is used if we do not have any policies .
*/
# define IPV6_ADDR_LABEL_DEFAULT 0xffffffffUL
static const __initdata struct ip6addrlbl_init_table
{
const struct in6_addr * prefix ;
int prefixlen ;
u32 label ;
} ip6addrlbl_init_table [ ] = {
{ /* ::/0 */
. prefix = & in6addr_any ,
. label = 1 ,
} , { /* fc00::/7 */
. prefix = & ( struct in6_addr ) { { { 0xfc } } } ,
. prefixlen = 7 ,
. label = 5 ,
} , { /* 2002::/16 */
. prefix = & ( struct in6_addr ) { { { 0x20 , 0x02 } } } ,
. prefixlen = 16 ,
. label = 2 ,
} , { /* 2001::/32 */
. prefix = & ( struct in6_addr ) { { { 0x20 , 0x01 } } } ,
. prefixlen = 32 ,
. label = 6 ,
} , { /* ::ffff:0:0 */
. prefix = & ( struct in6_addr ) { { { [ 10 ] = 0xff , [ 11 ] = 0xff } } } ,
. prefixlen = 96 ,
. label = 4 ,
} , { /* ::/96 */
. prefix = & in6addr_any ,
. prefixlen = 96 ,
. label = 3 ,
} , { /* ::1/128 */
. prefix = & in6addr_loopback ,
. prefixlen = 128 ,
. label = 0 ,
}
} ;
/* Object management */
static inline void ip6addrlbl_free ( struct ip6addrlbl_entry * p )
{
kfree ( p ) ;
}
static inline int ip6addrlbl_hold ( struct ip6addrlbl_entry * p )
{
return atomic_inc_not_zero ( & p - > refcnt ) ;
}
static inline void ip6addrlbl_put ( struct ip6addrlbl_entry * p )
{
if ( atomic_dec_and_test ( & p - > refcnt ) )
ip6addrlbl_free ( p ) ;
}
static void ip6addrlbl_free_rcu ( struct rcu_head * h )
{
ip6addrlbl_free ( container_of ( h , struct ip6addrlbl_entry , rcu ) ) ;
}
/* Find label */
static int __ip6addrlbl_match ( struct ip6addrlbl_entry * p ,
const struct in6_addr * addr ,
int addrtype , int ifindex )
{
if ( p - > ifindex & & p - > ifindex ! = ifindex )
return 0 ;
if ( p - > addrtype & & p - > addrtype ! = addrtype )
return 0 ;
if ( ! ipv6_prefix_equal ( addr , & p - > prefix , p - > prefixlen ) )
return 0 ;
return 1 ;
}
static struct ip6addrlbl_entry * __ipv6_addr_label ( const struct in6_addr * addr ,
int type , int ifindex )
{
struct hlist_node * pos ;
struct ip6addrlbl_entry * p ;
hlist_for_each_entry_rcu ( p , pos , & ip6addrlbl_table . head , list ) {
if ( __ip6addrlbl_match ( p , addr , type , ifindex ) )
return p ;
}
return NULL ;
}
u32 ipv6_addr_label ( const struct in6_addr * addr , int type , int ifindex )
{
u32 label ;
struct ip6addrlbl_entry * p ;
type & = IPV6_ADDR_MAPPED | IPV6_ADDR_COMPATv4 | IPV6_ADDR_LOOPBACK ;
rcu_read_lock ( ) ;
p = __ipv6_addr_label ( addr , type , ifindex ) ;
label = p ? p - > label : IPV6_ADDR_LABEL_DEFAULT ;
rcu_read_unlock ( ) ;
ADDRLABEL ( KERN_DEBUG " %s(addr= " NIP6_FMT " , type=%d, ifindex=%d) => %08x \n " ,
__FUNCTION__ ,
NIP6 ( * addr ) , type , ifindex ,
label ) ;
return label ;
}
/* allocate one entry */
2008-01-22 11:12:50 +03:00
static struct ip6addrlbl_entry * ip6addrlbl_alloc ( const struct in6_addr * prefix ,
int prefixlen , int ifindex ,
u32 label )
2007-11-14 09:56:23 +03:00
{
struct ip6addrlbl_entry * newp ;
int addrtype ;
ADDRLABEL ( KERN_DEBUG " %s(prefix= " NIP6_FMT " , prefixlen=%d, ifindex=%d, label=%u) \n " ,
__FUNCTION__ ,
NIP6 ( * prefix ) , prefixlen ,
ifindex ,
( unsigned int ) label ) ;
addrtype = ipv6_addr_type ( prefix ) & ( IPV6_ADDR_MAPPED | IPV6_ADDR_COMPATv4 | IPV6_ADDR_LOOPBACK ) ;
switch ( addrtype ) {
case IPV6_ADDR_MAPPED :
if ( prefixlen > 96 )
return ERR_PTR ( - EINVAL ) ;
if ( prefixlen < 96 )
addrtype = 0 ;
break ;
case IPV6_ADDR_COMPATv4 :
if ( prefixlen ! = 96 )
addrtype = 0 ;
break ;
case IPV6_ADDR_LOOPBACK :
if ( prefixlen ! = 128 )
addrtype = 0 ;
break ;
}
newp = kmalloc ( sizeof ( * newp ) , GFP_KERNEL ) ;
if ( ! newp )
return ERR_PTR ( - ENOMEM ) ;
ipv6_addr_prefix ( & newp - > prefix , prefix , prefixlen ) ;
newp - > prefixlen = prefixlen ;
newp - > ifindex = ifindex ;
newp - > addrtype = addrtype ;
newp - > label = label ;
INIT_HLIST_NODE ( & newp - > list ) ;
atomic_set ( & newp - > refcnt , 1 ) ;
return newp ;
}
/* add a label */
2008-01-22 11:12:50 +03:00
static int __ip6addrlbl_add ( struct ip6addrlbl_entry * newp , int replace )
2007-11-14 09:56:23 +03:00
{
int ret = 0 ;
ADDRLABEL ( KERN_DEBUG " %s(newp=%p, replace=%d) \n " ,
__FUNCTION__ ,
newp , replace ) ;
if ( hlist_empty ( & ip6addrlbl_table . head ) ) {
hlist_add_head_rcu ( & newp - > list , & ip6addrlbl_table . head ) ;
} else {
struct hlist_node * pos , * n ;
struct ip6addrlbl_entry * p = NULL ;
hlist_for_each_entry_safe ( p , pos , n ,
& ip6addrlbl_table . head , list ) {
if ( p - > prefixlen = = newp - > prefixlen & &
p - > ifindex = = newp - > ifindex & &
ipv6_addr_equal ( & p - > prefix , & newp - > prefix ) ) {
if ( ! replace ) {
ret = - EEXIST ;
goto out ;
}
hlist_replace_rcu ( & p - > list , & newp - > list ) ;
ip6addrlbl_put ( p ) ;
call_rcu ( & p - > rcu , ip6addrlbl_free_rcu ) ;
goto out ;
} else if ( ( p - > prefixlen = = newp - > prefixlen & & ! p - > ifindex ) | |
( p - > prefixlen < newp - > prefixlen ) ) {
hlist_add_before_rcu ( & newp - > list , & p - > list ) ;
goto out ;
}
}
hlist_add_after_rcu ( & p - > list , & newp - > list ) ;
}
out :
if ( ! ret )
ip6addrlbl_table . seq + + ;
return ret ;
}
/* add a label */
2008-01-22 11:12:50 +03:00
static int ip6addrlbl_add ( const struct in6_addr * prefix , int prefixlen ,
int ifindex , u32 label , int replace )
2007-11-14 09:56:23 +03:00
{
struct ip6addrlbl_entry * newp ;
int ret = 0 ;
ADDRLABEL ( KERN_DEBUG " %s(prefix= " NIP6_FMT " , prefixlen=%d, ifindex=%d, label=%u, replace=%d) \n " ,
__FUNCTION__ ,
NIP6 ( * prefix ) , prefixlen ,
ifindex ,
( unsigned int ) label ,
replace ) ;
newp = ip6addrlbl_alloc ( prefix , prefixlen , ifindex , label ) ;
if ( IS_ERR ( newp ) )
return PTR_ERR ( newp ) ;
spin_lock ( & ip6addrlbl_table . lock ) ;
ret = __ip6addrlbl_add ( newp , replace ) ;
spin_unlock ( & ip6addrlbl_table . lock ) ;
if ( ret )
ip6addrlbl_free ( newp ) ;
return ret ;
}
/* remove a label */
2008-01-22 11:12:50 +03:00
static int __ip6addrlbl_del ( const struct in6_addr * prefix , int prefixlen ,
int ifindex )
2007-11-14 09:56:23 +03:00
{
struct ip6addrlbl_entry * p = NULL ;
struct hlist_node * pos , * n ;
int ret = - ESRCH ;
ADDRLABEL ( KERN_DEBUG " %s(prefix= " NIP6_FMT " , prefixlen=%d, ifindex=%d) \n " ,
__FUNCTION__ ,
NIP6 ( * prefix ) , prefixlen ,
ifindex ) ;
hlist_for_each_entry_safe ( p , pos , n , & ip6addrlbl_table . head , list ) {
if ( p - > prefixlen = = prefixlen & &
p - > ifindex = = ifindex & &
ipv6_addr_equal ( & p - > prefix , prefix ) ) {
hlist_del_rcu ( & p - > list ) ;
ip6addrlbl_put ( p ) ;
call_rcu ( & p - > rcu , ip6addrlbl_free_rcu ) ;
ret = 0 ;
break ;
}
}
return ret ;
}
2008-01-22 11:12:50 +03:00
static int ip6addrlbl_del ( const struct in6_addr * prefix , int prefixlen ,
int ifindex )
2007-11-14 09:56:23 +03:00
{
struct in6_addr prefix_buf ;
int ret ;
ADDRLABEL ( KERN_DEBUG " %s(prefix= " NIP6_FMT " , prefixlen=%d, ifindex=%d) \n " ,
__FUNCTION__ ,
NIP6 ( * prefix ) , prefixlen ,
ifindex ) ;
ipv6_addr_prefix ( & prefix_buf , prefix , prefixlen ) ;
spin_lock ( & ip6addrlbl_table . lock ) ;
ret = __ip6addrlbl_del ( & prefix_buf , prefixlen , ifindex ) ;
spin_unlock ( & ip6addrlbl_table . lock ) ;
return ret ;
}
/* add default label */
static __init int ip6addrlbl_init ( void )
{
int err = 0 ;
int i ;
ADDRLABEL ( KERN_DEBUG " %s() \n " , __FUNCTION__ ) ;
for ( i = 0 ; i < ARRAY_SIZE ( ip6addrlbl_init_table ) ; i + + ) {
int ret = ip6addrlbl_add ( ip6addrlbl_init_table [ i ] . prefix ,
ip6addrlbl_init_table [ i ] . prefixlen ,
0 ,
ip6addrlbl_init_table [ i ] . label , 0 ) ;
/* XXX: should we free all rules when we catch an error? */
if ( ret & & ( ! err | | err ! = - ENOMEM ) )
err = ret ;
}
return err ;
}
int __init ipv6_addr_label_init ( void )
{
spin_lock_init ( & ip6addrlbl_table . lock ) ;
return ip6addrlbl_init ( ) ;
}
static const struct nla_policy ifal_policy [ IFAL_MAX + 1 ] = {
[ IFAL_ADDRESS ] = { . len = sizeof ( struct in6_addr ) , } ,
[ IFAL_LABEL ] = { . len = sizeof ( u32 ) , } ,
} ;
static int ip6addrlbl_newdel ( struct sk_buff * skb , struct nlmsghdr * nlh ,
void * arg )
{
2007-11-30 16:21:31 +03:00
struct net * net = skb - > sk - > sk_net ;
2007-11-14 09:56:23 +03:00
struct ifaddrlblmsg * ifal ;
struct nlattr * tb [ IFAL_MAX + 1 ] ;
struct in6_addr * pfx ;
u32 label ;
int err = 0 ;
2007-11-30 16:21:31 +03:00
if ( net ! = & init_net )
return 0 ;
2007-11-14 09:56:23 +03:00
err = nlmsg_parse ( nlh , sizeof ( * ifal ) , tb , IFAL_MAX , ifal_policy ) ;
if ( err < 0 )
return err ;
ifal = nlmsg_data ( nlh ) ;
if ( ifal - > ifal_family ! = AF_INET6 | |
ifal - > ifal_prefixlen > 128 )
return - EINVAL ;
if ( ifal - > ifal_index & &
! __dev_get_by_index ( & init_net , ifal - > ifal_index ) )
return - EINVAL ;
if ( ! tb [ IFAL_ADDRESS ] )
return - EINVAL ;
pfx = nla_data ( tb [ IFAL_ADDRESS ] ) ;
if ( ! pfx )
return - EINVAL ;
if ( ! tb [ IFAL_LABEL ] )
return - EINVAL ;
label = nla_get_u32 ( tb [ IFAL_LABEL ] ) ;
if ( label = = IPV6_ADDR_LABEL_DEFAULT )
return - EINVAL ;
switch ( nlh - > nlmsg_type ) {
case RTM_NEWADDRLABEL :
err = ip6addrlbl_add ( pfx , ifal - > ifal_prefixlen ,
ifal - > ifal_index , label ,
nlh - > nlmsg_flags & NLM_F_REPLACE ) ;
break ;
case RTM_DELADDRLABEL :
err = ip6addrlbl_del ( pfx , ifal - > ifal_prefixlen ,
ifal - > ifal_index ) ;
break ;
default :
err = - EOPNOTSUPP ;
}
return err ;
}
static inline void ip6addrlbl_putmsg ( struct nlmsghdr * nlh ,
int prefixlen , int ifindex , u32 lseq )
{
struct ifaddrlblmsg * ifal = nlmsg_data ( nlh ) ;
ifal - > ifal_family = AF_INET6 ;
ifal - > ifal_prefixlen = prefixlen ;
ifal - > ifal_flags = 0 ;
ifal - > ifal_index = ifindex ;
ifal - > ifal_seq = lseq ;
} ;
static int ip6addrlbl_fill ( struct sk_buff * skb ,
struct ip6addrlbl_entry * p ,
u32 lseq ,
u32 pid , u32 seq , int event ,
unsigned int flags )
{
struct nlmsghdr * nlh = nlmsg_put ( skb , pid , seq , event ,
sizeof ( struct ifaddrlblmsg ) , flags ) ;
if ( ! nlh )
return - EMSGSIZE ;
ip6addrlbl_putmsg ( nlh , p - > prefixlen , p - > ifindex , lseq ) ;
if ( nla_put ( skb , IFAL_ADDRESS , 16 , & p - > prefix ) < 0 | |
nla_put_u32 ( skb , IFAL_LABEL , p - > label ) < 0 ) {
nlmsg_cancel ( skb , nlh ) ;
return - EMSGSIZE ;
}
return nlmsg_end ( skb , nlh ) ;
}
static int ip6addrlbl_dump ( struct sk_buff * skb , struct netlink_callback * cb )
{
2007-11-30 16:21:31 +03:00
struct net * net = skb - > sk - > sk_net ;
2007-11-14 09:56:23 +03:00
struct ip6addrlbl_entry * p ;
struct hlist_node * pos ;
int idx = 0 , s_idx = cb - > args [ 0 ] ;
int err ;
2007-11-30 16:21:31 +03:00
if ( net ! = & init_net )
return 0 ;
2007-11-14 09:56:23 +03:00
rcu_read_lock ( ) ;
hlist_for_each_entry_rcu ( p , pos , & ip6addrlbl_table . head , list ) {
if ( idx > = s_idx ) {
if ( ( err = ip6addrlbl_fill ( skb , p ,
ip6addrlbl_table . seq ,
NETLINK_CB ( cb - > skb ) . pid ,
cb - > nlh - > nlmsg_seq ,
RTM_NEWADDRLABEL ,
NLM_F_MULTI ) ) < = 0 )
break ;
}
idx + + ;
}
rcu_read_unlock ( ) ;
cb - > args [ 0 ] = idx ;
return skb - > len ;
}
static inline int ip6addrlbl_msgsize ( void )
{
return ( NLMSG_ALIGN ( sizeof ( struct ifaddrlblmsg ) )
+ nla_total_size ( 16 ) /* IFAL_ADDRESS */
+ nla_total_size ( 4 ) /* IFAL_LABEL */
) ;
}
static int ip6addrlbl_get ( struct sk_buff * in_skb , struct nlmsghdr * nlh ,
void * arg )
{
2007-11-30 16:21:31 +03:00
struct net * net = in_skb - > sk - > sk_net ;
2007-11-14 09:56:23 +03:00
struct ifaddrlblmsg * ifal ;
struct nlattr * tb [ IFAL_MAX + 1 ] ;
struct in6_addr * addr ;
u32 lseq ;
int err = 0 ;
struct ip6addrlbl_entry * p ;
struct sk_buff * skb ;
2007-11-30 16:21:31 +03:00
if ( net ! = & init_net )
return 0 ;
2007-11-14 09:56:23 +03:00
err = nlmsg_parse ( nlh , sizeof ( * ifal ) , tb , IFAL_MAX , ifal_policy ) ;
if ( err < 0 )
return err ;
ifal = nlmsg_data ( nlh ) ;
if ( ifal - > ifal_family ! = AF_INET6 | |
ifal - > ifal_prefixlen ! = 128 )
return - EINVAL ;
if ( ifal - > ifal_index & &
! __dev_get_by_index ( & init_net , ifal - > ifal_index ) )
return - EINVAL ;
if ( ! tb [ IFAL_ADDRESS ] )
return - EINVAL ;
addr = nla_data ( tb [ IFAL_ADDRESS ] ) ;
if ( ! addr )
return - EINVAL ;
rcu_read_lock ( ) ;
p = __ipv6_addr_label ( addr , ipv6_addr_type ( addr ) , ifal - > ifal_index ) ;
if ( p & & ip6addrlbl_hold ( p ) )
p = NULL ;
lseq = ip6addrlbl_table . seq ;
rcu_read_unlock ( ) ;
if ( ! p ) {
err = - ESRCH ;
goto out ;
}
if ( ! ( skb = nlmsg_new ( ip6addrlbl_msgsize ( ) , GFP_KERNEL ) ) ) {
ip6addrlbl_put ( p ) ;
return - ENOBUFS ;
}
err = ip6addrlbl_fill ( skb , p , lseq ,
NETLINK_CB ( in_skb ) . pid , nlh - > nlmsg_seq ,
RTM_NEWADDRLABEL , 0 ) ;
ip6addrlbl_put ( p ) ;
if ( err < 0 ) {
WARN_ON ( err = = - EMSGSIZE ) ;
kfree_skb ( skb ) ;
goto out ;
}
2007-11-20 09:26:51 +03:00
err = rtnl_unicast ( skb , & init_net , NETLINK_CB ( in_skb ) . pid ) ;
2007-11-14 09:56:23 +03:00
out :
return err ;
}
void __init ipv6_addr_label_rtnl_register ( void )
{
__rtnl_register ( PF_INET6 , RTM_NEWADDRLABEL , ip6addrlbl_newdel , NULL ) ;
__rtnl_register ( PF_INET6 , RTM_DELADDRLABEL , ip6addrlbl_newdel , NULL ) ;
__rtnl_register ( PF_INET6 , RTM_GETADDRLABEL , ip6addrlbl_get , ip6addrlbl_dump ) ;
}