2006-08-04 14:39:02 +04:00
/*
* net / ipv6 / fib6_rules . c IPv6 Routing Policy Rules
*
* Copyright ( C ) 2003 - 2006 Helsinki University of Technology
* Copyright ( C ) 2003 - 2006 USAGI / WIDE Project
*
* 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 , version 2.
*
* Authors
* Thomas Graf < tgraf @ suug . ch >
* Ville Nuorvala < vnuorval @ tcs . hut . fi >
*/
# include <linux/netdevice.h>
# include <net/fib_rules.h>
# include <net/ipv6.h>
2007-04-06 22:45:39 +04:00
# include <net/addrconf.h>
2006-08-04 14:39:02 +04:00
# include <net/ip6_route.h>
# include <net/netlink.h>
struct fib6_rule
{
struct fib_rule common ;
struct rt6key src ;
struct rt6key dst ;
u8 tclass ;
} ;
2008-03-04 10:25:27 +03:00
struct dst_entry * fib6_rule_lookup ( struct net * net , struct flowi * fl ,
int flags , pol_lookup_t lookup )
2006-08-04 14:39:02 +04:00
{
struct fib_lookup_arg arg = {
. lookup_ptr = lookup ,
} ;
2008-03-04 10:33:08 +03:00
fib_rules_lookup ( net - > ipv6 . fib6_rules_ops , fl , flags , & arg ) ;
2006-08-04 14:39:02 +04:00
if ( arg . rule )
fib_rule_put ( arg . rule ) ;
2006-08-09 03:44:17 +04:00
if ( arg . result )
2006-10-19 08:50:09 +04:00
return arg . result ;
2006-08-09 03:44:17 +04:00
2008-03-05 00:48:30 +03:00
dst_hold ( & net - > ipv6 . ip6_null_entry - > u . dst ) ;
return & net - > ipv6 . ip6_null_entry - > u . dst ;
2006-08-04 14:39:02 +04:00
}
2006-08-08 08:50:48 +04:00
static int fib6_rule_action ( struct fib_rule * rule , struct flowi * flp ,
int flags , struct fib_lookup_arg * arg )
2006-08-04 14:39:02 +04:00
{
struct rt6_info * rt = NULL ;
struct fib6_table * table ;
2008-03-05 00:48:30 +03:00
struct net * net = rule - > fr_net ;
2006-08-04 14:39:02 +04:00
pol_lookup_t lookup = arg - > lookup_ptr ;
switch ( rule - > action ) {
case FR_ACT_TO_TBL :
break ;
case FR_ACT_UNREACHABLE :
2008-03-05 00:48:30 +03:00
rt = net - > ipv6 . ip6_null_entry ;
2006-08-04 14:39:02 +04:00
goto discard_pkt ;
default :
case FR_ACT_BLACKHOLE :
2008-03-05 00:48:30 +03:00
rt = net - > ipv6 . ip6_blk_hole_entry ;
2006-08-04 14:39:02 +04:00
goto discard_pkt ;
case FR_ACT_PROHIBIT :
2008-03-05 00:48:30 +03:00
rt = net - > ipv6 . ip6_prohibit_entry ;
2006-08-04 14:39:02 +04:00
goto discard_pkt ;
}
2008-03-05 00:48:30 +03:00
table = fib6_get_table ( net , rule - > table ) ;
2006-08-04 14:39:02 +04:00
if ( table )
2008-03-05 00:48:30 +03:00
rt = lookup ( net , table , flp , flags ) ;
2006-08-04 14:39:02 +04:00
2008-03-05 00:48:30 +03:00
if ( rt ! = net - > ipv6 . ip6_null_entry ) {
2007-04-06 22:45:39 +04:00
struct fib6_rule * r = ( struct fib6_rule * ) rule ;
/*
* If we need to find a source address for this traffic ,
* we check the result if it meets requirement of the rule .
*/
if ( ( rule - > flags & FIB_RULE_FIND_SADDR ) & &
r - > src . plen & & ! ( flags & RT6_LOOKUP_F_HAS_SADDR ) ) {
struct in6_addr saddr ;
2008-03-25 03:37:42 +03:00
unsigned int srcprefs = 0 ;
if ( flags & RT6_LOOKUP_F_SRCPREF_TMP )
srcprefs | = IPV6_PREFER_SRC_TMP ;
if ( flags & RT6_LOOKUP_F_SRCPREF_PUBLIC )
srcprefs | = IPV6_PREFER_SRC_PUBLIC ;
if ( flags & RT6_LOOKUP_F_SRCPREF_COA )
srcprefs | = IPV6_PREFER_SRC_COA ;
2008-03-03 15:44:34 +03:00
if ( ipv6_dev_get_saddr ( ip6_dst_idev ( & rt - > u . dst ) - > dev ,
2008-03-25 03:37:42 +03:00
& flp - > fl6_dst , srcprefs ,
& saddr ) )
2007-04-06 22:45:39 +04:00
goto again ;
if ( ! ipv6_prefix_equal ( & saddr , & r - > src . addr ,
r - > src . plen ) )
goto again ;
ipv6_addr_copy ( & flp - > fl6_src , & saddr ) ;
}
2006-08-04 14:39:02 +04:00
goto out ;
2007-04-06 22:45:39 +04:00
}
again :
2006-08-04 14:39:02 +04:00
dst_release ( & rt - > u . dst ) ;
2006-08-07 09:24:08 +04:00
rt = NULL ;
goto out ;
2006-08-04 14:39:02 +04:00
discard_pkt :
dst_hold ( & rt - > u . dst ) ;
out :
arg - > result = rt ;
return rt = = NULL ? - EAGAIN : 0 ;
}
static int fib6_rule_match ( struct fib_rule * rule , struct flowi * fl , int flags )
{
struct fib6_rule * r = ( struct fib6_rule * ) rule ;
2006-10-14 02:01:03 +04:00
if ( r - > dst . plen & &
! ipv6_prefix_equal ( & fl - > fl6_dst , & r - > dst . addr , r - > dst . plen ) )
2006-08-04 14:39:02 +04:00
return 0 ;
2007-04-06 22:45:39 +04:00
/*
* If FIB_RULE_FIND_SADDR is set and we do not have a
* source address for the traffic , we defer check for
* source address .
*/
2006-10-14 02:01:03 +04:00
if ( r - > src . plen ) {
2007-04-06 22:45:39 +04:00
if ( flags & RT6_LOOKUP_F_HAS_SADDR ) {
if ( ! ipv6_prefix_equal ( & fl - > fl6_src , & r - > src . addr ,
r - > src . plen ) )
return 0 ;
} else if ( ! ( r - > common . flags & FIB_RULE_FIND_SADDR ) )
2006-10-14 02:01:03 +04:00
return 0 ;
}
2006-08-04 14:39:02 +04:00
2006-08-21 14:18:57 +04:00
if ( r - > tclass & & r - > tclass ! = ( ( ntohl ( fl - > fl6_flowlabel ) > > 20 ) & 0xff ) )
return 0 ;
2006-08-04 14:39:02 +04:00
return 1 ;
}
2007-06-05 23:38:30 +04:00
static const struct nla_policy fib6_rule_policy [ FRA_MAX + 1 ] = {
2006-11-10 02:22:48 +03:00
FRA_GENERIC_POLICY ,
2006-08-04 14:39:02 +04:00
} ;
static int fib6_rule_configure ( struct fib_rule * rule , struct sk_buff * skb ,
struct nlmsghdr * nlh , struct fib_rule_hdr * frh ,
struct nlattr * * tb )
{
int err = - EINVAL ;
2008-03-25 20:26:21 +03:00
struct net * net = sock_net ( skb - > sk ) ;
2006-08-04 14:39:02 +04:00
struct fib6_rule * rule6 = ( struct fib6_rule * ) rule ;
if ( rule - > action = = FR_ACT_TO_TBL ) {
if ( rule - > table = = RT6_TABLE_UNSPEC )
goto errout ;
2008-03-04 10:33:08 +03:00
if ( fib6_new_table ( net , rule - > table ) = = NULL ) {
2006-08-04 14:39:02 +04:00
err = - ENOBUFS ;
goto errout ;
}
}
2007-03-24 22:46:02 +03:00
if ( frh - > src_len )
2006-08-04 14:39:02 +04:00
nla_memcpy ( & rule6 - > src . addr , tb [ FRA_SRC ] ,
sizeof ( struct in6_addr ) ) ;
2007-03-24 22:46:02 +03:00
if ( frh - > dst_len )
2006-08-04 14:39:02 +04:00
nla_memcpy ( & rule6 - > dst . addr , tb [ FRA_DST ] ,
sizeof ( struct in6_addr ) ) ;
rule6 - > src . plen = frh - > src_len ;
rule6 - > dst . plen = frh - > dst_len ;
rule6 - > tclass = frh - > tos ;
err = 0 ;
errout :
return err ;
}
static int fib6_rule_compare ( struct fib_rule * rule , struct fib_rule_hdr * frh ,
struct nlattr * * tb )
{
struct fib6_rule * rule6 = ( struct fib6_rule * ) rule ;
if ( frh - > src_len & & ( rule6 - > src . plen ! = frh - > src_len ) )
return 0 ;
if ( frh - > dst_len & & ( rule6 - > dst . plen ! = frh - > dst_len ) )
return 0 ;
if ( frh - > tos & & ( rule6 - > tclass ! = frh - > tos ) )
return 0 ;
2007-03-24 22:46:02 +03:00
if ( frh - > src_len & &
2006-08-04 14:39:02 +04:00
nla_memcmp ( tb [ FRA_SRC ] , & rule6 - > src . addr , sizeof ( struct in6_addr ) ) )
return 0 ;
2007-03-24 22:46:02 +03:00
if ( frh - > dst_len & &
2006-08-04 14:39:02 +04:00
nla_memcmp ( tb [ FRA_DST ] , & rule6 - > dst . addr , sizeof ( struct in6_addr ) ) )
return 0 ;
return 1 ;
}
static int fib6_rule_fill ( struct fib_rule * rule , struct sk_buff * skb ,
struct nlmsghdr * nlh , struct fib_rule_hdr * frh )
{
struct fib6_rule * rule6 = ( struct fib6_rule * ) rule ;
frh - > family = AF_INET6 ;
frh - > dst_len = rule6 - > dst . plen ;
frh - > src_len = rule6 - > src . plen ;
frh - > tos = rule6 - > tclass ;
if ( rule6 - > dst . plen )
NLA_PUT ( skb , FRA_DST , sizeof ( struct in6_addr ) ,
& rule6 - > dst . addr ) ;
if ( rule6 - > src . plen )
NLA_PUT ( skb , FRA_SRC , sizeof ( struct in6_addr ) ,
& rule6 - > src . addr ) ;
return 0 ;
nla_put_failure :
return - ENOBUFS ;
}
2008-01-10 14:18:25 +03:00
static u32 fib6_rule_default_pref ( struct fib_rules_ops * ops )
2006-08-04 14:39:02 +04:00
{
return 0x3FFF ;
}
2006-11-11 01:10:15 +03:00
static size_t fib6_rule_nlmsg_payload ( struct fib_rule * rule )
{
return nla_total_size ( 16 ) /* dst */
+ nla_total_size ( 16 ) ; /* src */
}
2008-03-04 10:32:30 +03:00
static struct fib_rules_ops fib6_rules_ops_template = {
2006-08-04 14:39:02 +04:00
. family = AF_INET6 ,
. rule_size = sizeof ( struct fib6_rule ) ,
2007-03-24 22:46:02 +03:00
. addr_size = sizeof ( struct in6_addr ) ,
2006-08-04 14:39:02 +04:00
. action = fib6_rule_action ,
. match = fib6_rule_match ,
. configure = fib6_rule_configure ,
. compare = fib6_rule_compare ,
. fill = fib6_rule_fill ,
. default_pref = fib6_rule_default_pref ,
2006-11-11 01:10:15 +03:00
. nlmsg_payload = fib6_rule_nlmsg_payload ,
2006-08-04 14:39:02 +04:00
. nlgroup = RTNLGRP_IPV6_RULE ,
. policy = fib6_rule_policy ,
. owner = THIS_MODULE ,
2008-01-21 03:46:01 +03:00
. fro_net = & init_net ,
2006-08-04 14:39:02 +04:00
} ;
2008-03-04 10:33:08 +03:00
static int fib6_rules_net_init ( struct net * net )
2006-08-04 14:39:02 +04:00
{
2008-03-04 10:33:08 +03:00
int err = - ENOMEM ;
2007-11-11 09:12:03 +03:00
2008-03-04 10:33:08 +03:00
net - > ipv6 . fib6_rules_ops = kmemdup ( & fib6_rules_ops_template ,
sizeof ( * net - > ipv6 . fib6_rules_ops ) ,
GFP_KERNEL ) ;
if ( ! net - > ipv6 . fib6_rules_ops )
goto out ;
2008-03-04 10:32:30 +03:00
2008-03-04 10:33:08 +03:00
net - > ipv6 . fib6_rules_ops - > fro_net = net ;
INIT_LIST_HEAD ( & net - > ipv6 . fib6_rules_ops - > rules_list ) ;
2008-03-04 10:32:30 +03:00
2008-03-04 10:33:08 +03:00
err = fib_default_rule_add ( net - > ipv6 . fib6_rules_ops , 0 ,
2007-11-11 09:12:03 +03:00
RT6_TABLE_LOCAL , FIB_RULE_PERMANENT ) ;
2008-03-04 10:33:08 +03:00
if ( err )
goto out_fib6_rules_ops ;
2007-12-07 11:42:52 +03:00
2008-03-04 10:33:08 +03:00
err = fib_default_rule_add ( net - > ipv6 . fib6_rules_ops ,
0x7FFE , RT6_TABLE_MAIN , 0 ) ;
if ( err )
goto out_fib6_default_rule_add ;
2007-12-07 11:42:52 +03:00
2008-03-04 10:33:08 +03:00
err = fib_rules_register ( net - > ipv6 . fib6_rules_ops ) ;
if ( err )
goto out_fib6_default_rule_add ;
2007-12-07 11:42:52 +03:00
out :
2008-03-04 10:33:08 +03:00
return err ;
2007-12-07 11:42:52 +03:00
2008-03-04 10:33:08 +03:00
out_fib6_default_rule_add :
fib_rules_cleanup_ops ( net - > ipv6 . fib6_rules_ops ) ;
out_fib6_rules_ops :
kfree ( net - > ipv6 . fib6_rules_ops ) ;
2007-12-07 11:42:52 +03:00
goto out ;
2006-08-04 14:39:02 +04:00
}
2008-03-04 10:33:08 +03:00
static void fib6_rules_net_exit ( struct net * net )
{
fib_rules_unregister ( net - > ipv6 . fib6_rules_ops ) ;
kfree ( net - > ipv6 . fib6_rules_ops ) ;
}
static struct pernet_operations fib6_rules_net_ops = {
. init = fib6_rules_net_init ,
. exit = fib6_rules_net_exit ,
} ;
int __init fib6_rules_init ( void )
{
return register_pernet_subsys ( & fib6_rules_net_ops ) ;
}
2006-08-04 14:39:02 +04:00
void fib6_rules_cleanup ( void )
{
2008-03-04 10:33:08 +03:00
return unregister_pernet_subsys ( & fib6_rules_net_ops ) ;
2006-08-04 14:39:02 +04:00
}