@ -24,6 +24,9 @@
# include <net/nexthop.h>
# include "internal.h"
/* max memory we will use for mpls_route */
# define MAX_MPLS_ROUTE_MEM 4096
/* Maximum number of labels to look ahead at when selecting a path of
* a multipath route
*/
@ -60,10 +63,7 @@ EXPORT_SYMBOL_GPL(mpls_output_possible);
static u8 * __mpls_nh_via ( struct mpls_route * rt , struct mpls_nh * nh )
{
u8 * nh0_via = PTR_ALIGN ( ( u8 * ) & rt - > rt_nh [ rt - > rt_nhn ] , VIA_ALEN_ALIGN ) ;
int nh_index = nh - rt - > rt_nh ;
return nh0_via + rt - > rt_max_alen * nh_index ;
return ( u8 * ) nh + rt - > rt_via_offset ;
}
static const u8 * mpls_nh_via ( const struct mpls_route * rt ,
@ -189,21 +189,32 @@ static u32 mpls_multipath_hash(struct mpls_route *rt, struct sk_buff *skb)
return hash ;
}
static struct mpls_nh * mpls_get_nexthop ( struct mpls_route * rt , u8 index )
{
return ( struct mpls_nh * ) ( ( u8 * ) rt - > rt_nh + index * rt - > rt_nh_size ) ;
}
/* number of alive nexthops (rt->rt_nhn_alive) and the flags for
* a next hop ( nh - > nh_flags ) are modified by netdev event handlers .
* Since those fields can change at any moment , use READ_ONCE to
* access both .
*/
static struct mpls_nh * mpls_select_multipath ( struct mpls_route * rt ,
struct sk_buff * skb )
{
int alive = ACCESS_ONCE ( rt - > rt_nhn_alive ) ;
u32 hash = 0 ;
int nh_index = 0 ;
int n = 0 ;
u8 alive ;
/* No need to look further into packet if there's only
* one path
*/
if ( rt - > rt_nhn = = 1 )
goto out ;
return rt - > rt_nh ;
if ( alive < = 0 )
alive = READ_ONCE ( rt - > rt_nhn_alive ) ;
if ( alive = = 0 )
return NULL ;
hash = mpls_multipath_hash ( rt , skb ) ;
@ -211,7 +222,9 @@ static struct mpls_nh *mpls_select_multipath(struct mpls_route *rt,
if ( alive = = rt - > rt_nhn )
goto out ;
for_nexthops ( rt ) {
if ( nh - > nh_flags & ( RTNH_F_DEAD | RTNH_F_LINKDOWN ) )
unsigned int nh_flags = READ_ONCE ( nh - > nh_flags ) ;
if ( nh_flags & ( RTNH_F_DEAD | RTNH_F_LINKDOWN ) )
continue ;
if ( n = = nh_index )
return nh ;
@ -219,7 +232,7 @@ static struct mpls_nh *mpls_select_multipath(struct mpls_route *rt,
} endfor_nexthops ( rt ) ;
out :
return & rt - > rt_nh [ nh_index ] ;
return mpls_get_nexthop ( rt , nh_index ) ;
}
static bool mpls_egress ( struct net * net , struct mpls_route * rt ,
@ -458,20 +471,27 @@ struct mpls_route_config {
int rc_mp_len ;
} ;
static struct mpls_route * mpls_rt_alloc ( int num_nh , u8 max_alen )
/* all nexthops within a route have the same size based on max
* number of labels and max via length for a hop
*/
static struct mpls_route * mpls_rt_alloc ( u8 num_nh , u8 max_alen , u8 max_labels )
{
u8 max_alen_aligned = ALIGN ( max_alen , VIA_ALEN_ALIGN ) ;
u8 nh_size = MPLS_NH_SIZE ( max_labels , max_alen ) ;
struct mpls_route * rt ;
size_t size ;
rt = kzalloc ( ALIGN ( sizeof ( * rt ) + num_nh * sizeof ( * rt - > rt_nh ) ,
VIA_ALEN_ALIGN ) +
num_nh * max_alen_aligned ,
GFP_KERNEL ) ;
if ( rt ) {
rt - > rt_nhn = num_nh ;
rt - > rt_nhn_alive = num_nh ;
rt - > rt_max_alen = max_alen_aligned ;
}
size = sizeof ( * rt ) + num_nh * nh_size ;
if ( size > MAX_MPLS_ROUTE_MEM )
return ERR_PTR ( - EINVAL ) ;
rt = kzalloc ( size , GFP_KERNEL ) ;
if ( ! rt )
return ERR_PTR ( - ENOMEM ) ;
rt - > rt_nhn = num_nh ;
rt - > rt_nhn_alive = num_nh ;
rt - > rt_nh_size = nh_size ;
rt - > rt_via_offset = MPLS_NH_VIA_OFF ( max_labels ) ;
return rt ;
}
@ -676,9 +696,6 @@ static int mpls_nh_build_from_cfg(struct mpls_route_config *cfg,
return - ENOMEM ;
err = - EINVAL ;
/* Ensure only a supported number of labels are present */
if ( cfg - > rc_output_labels > MAX_NEW_LABELS )
goto errout ;
nh - > nh_labels = cfg - > rc_output_labels ;
for ( i = 0 ; i < nh - > nh_labels ; i + + )
@ -703,7 +720,7 @@ errout:
static int mpls_nh_build ( struct net * net , struct mpls_route * rt ,
struct mpls_nh * nh , int oif , struct nlattr * via ,
struct nlattr * newdst )
struct nlattr * newdst , u8 max_labels )
{
int err = - ENOMEM ;
@ -711,7 +728,7 @@ static int mpls_nh_build(struct net *net, struct mpls_route *rt,
goto errout ;
if ( newdst ) {
err = nla_get_labels ( newdst , MAX_NEW_LABELS ,
err = nla_get_labels ( newdst , max_labels ,
& nh - > nh_labels , nh - > nh_label ) ;
if ( err )
goto errout ;
@ -736,22 +753,20 @@ errout:
return err ;
}
static int mpls_count_nexthops ( struct rtnexthop * rtnh , int len ,
u8 cfg_via_alen , u8 * max_via_alen )
static u8 mpls_count_nexthops ( struct rtnexthop * rtnh , int len ,
u8 cfg_via_alen , u8 * max_via_alen ,
u8 * max_labels )
{
int nhs = 0 ;
int remaining = len ;
if ( ! rtnh ) {
* max_via_alen = cfg_via_alen ;
return 1 ;
}
u8 nhs = 0 ;
* max_via_alen = 0 ;
* max_labels = 0 ;
while ( rtnh_ok ( rtnh , remaining ) ) {
struct nlattr * nla , * attrs = rtnh_attrs ( rtnh ) ;
int attrlen ;
u8 n_labels = 0 ;
attrlen = rtnh_attrlen ( rtnh ) ;
nla = nla_find ( attrs , attrlen , RTA_VIA ) ;
@ -765,7 +780,20 @@ static int mpls_count_nexthops(struct rtnexthop *rtnh, int len,
via_alen ) ;
}
nla = nla_find ( attrs , attrlen , RTA_NEWDST ) ;
if ( nla & &
nla_get_labels ( nla , MAX_NEW_LABELS , & n_labels , NULL ) ! = 0 )
return 0 ;
* max_labels = max_t ( u8 , * max_labels , n_labels ) ;
/* number of nexthops is tracked by a u8.
* Check for overflow .
*/
if ( nhs = = 255 )
return 0 ;
nhs + + ;
rtnh = rtnh_next ( rtnh , & remaining ) ;
}
@ -774,13 +802,13 @@ static int mpls_count_nexthops(struct rtnexthop *rtnh, int len,
}
static int mpls_nh_build_multi ( struct mpls_route_config * cfg ,
struct mpls_route * rt )
struct mpls_route * rt , u8 max_labels )
{
struct rtnexthop * rtnh = cfg - > rc_mp ;
struct nlattr * nla_via , * nla_newdst ;
int remaining = cfg - > rc_mp_len ;
int nhs = 0 ;
int err = 0 ;
u8 nhs = 0 ;
change_nexthops ( rt ) {
int attrlen ;
@ -807,7 +835,8 @@ static int mpls_nh_build_multi(struct mpls_route_config *cfg,
}
err = mpls_nh_build ( cfg - > rc_nlinfo . nl_net , rt , nh ,
rtnh - > rtnh_ifindex , nla_via , nla_newdst ) ;
rtnh - > rtnh_ifindex , nla_via , nla_newdst ,
max_labels ) ;
if ( err )
goto errout ;
@ -834,7 +863,8 @@ static int mpls_route_add(struct mpls_route_config *cfg)
int err = - EINVAL ;
u8 max_via_alen ;
unsigned index ;
int nhs ;
u8 max_labels ;
u8 nhs ;
index = cfg - > rc_label ;
@ -872,22 +902,32 @@ static int mpls_route_add(struct mpls_route_config *cfg)
goto errout ;
err = - EINVAL ;
nhs = mpls_count_nexthops ( cfg - > rc_mp , cfg - > rc_mp_len ,
cfg - > rc_via_alen , & max_via_alen ) ;
if ( cfg - > rc_mp ) {
nhs = mpls_count_nexthops ( cfg - > rc_mp , cfg - > rc_mp_len ,
cfg - > rc_via_alen , & max_via_alen ,
& max_labels ) ;
} else {
max_via_alen = cfg - > rc_via_alen ;
max_labels = cfg - > rc_output_labels ;
nhs = 1 ;
}
if ( nhs = = 0 )
goto errout ;
err = - ENOMEM ;
rt = mpls_rt_alloc ( nhs , max_via_alen ) ;
if ( ! rt )
rt = mpls_rt_alloc ( nhs , max_via_alen , max_labels ) ;
if ( IS_ERR ( rt ) ) {
err = PTR_ERR ( rt ) ;
goto errout ;
}
rt - > rt_protocol = cfg - > rc_protocol ;
rt - > rt_payload_type = cfg - > rc_payload_type ;
rt - > rt_ttl_propagate = cfg - > rc_ttl_propagate ;
if ( cfg - > rc_mp )
err = mpls_nh_build_multi ( cfg , rt ) ;
err = mpls_nh_build_multi ( cfg , rt , max_labels ) ;
else
err = mpls_nh_build_from_cfg ( cfg , rt ) ;
if ( err )
@ -1302,8 +1342,7 @@ static void mpls_ifdown(struct net_device *dev, int event)
{
struct mpls_route __rcu * * platform_label ;
struct net * net = dev_net ( dev ) ;
unsigned int nh_flags = RTNH_F_DEAD | RTNH_F_LINKDOWN ;
unsigned int alive , deleted ;
u8 alive , deleted ;
unsigned index ;
platform_label = rtnl_dereference ( net - > mpls . platform_label ) ;
@ -1316,22 +1355,27 @@ static void mpls_ifdown(struct net_device *dev, int event)
alive = 0 ;
deleted = 0 ;
change_nexthops ( rt ) {
unsigned int nh_flags = nh - > nh_flags ;
if ( rtnl_dereference ( nh - > nh_dev ) ! = dev )
goto next ;
switch ( event ) {
case NETDEV_DOWN :
case NETDEV_UNREGISTER :
nh - > nh _flags | = RTNH_F_DEAD ;
nh_flags | = RTNH_F_DEAD ;
/* fall through */
case NETDEV_CHANGE :
nh - > nh _flags | = RTNH_F_LINKDOWN ;
nh_flags | = RTNH_F_LINKDOWN ;
break ;
}
if ( event = = NETDEV_UNREGISTER )
RCU_INIT_POINTER ( nh - > nh_dev , NULL ) ;
if ( nh - > nh_flags ! = nh_flags )
WRITE_ONCE ( nh - > nh_flags , nh_flags ) ;
next :
if ( ! ( nh - > nh_flags & nh_flags ) )
if ( ! ( nh_flags & ( RTNH_F_DEAD | RTNH_F_LINKDOWN ) ) )
alive + + ;
if ( ! rtnl_dereference ( nh - > nh_dev ) )
deleted + + ;
@ -1345,12 +1389,12 @@ next:
}
}
static void mpls_ifup ( struct net_device * dev , unsigned int nh_ flags)
static void mpls_ifup ( struct net_device * dev , unsigned int flags )
{
struct mpls_route __rcu * * platform_label ;
struct net * net = dev_net ( dev ) ;
unsigned index ;
int alive ;
u8 alive ;
platform_label = rtnl_dereference ( net - > mpls . platform_label ) ;
for ( index = 0 ; index < net - > mpls . platform_labels ; index + + ) {
@ -1361,20 +1405,22 @@ static void mpls_ifup(struct net_device *dev, unsigned int nh_flags)
alive = 0 ;
change_nexthops ( rt ) {
unsigned int nh_flags = nh - > nh_flags ;
struct net_device * nh_dev =
rtnl_dereference ( nh - > nh_dev ) ;
if ( ! ( nh - > nh _flags & nh_ flags) ) {
if ( ! ( nh_flags & flags ) ) {
alive + + ;
continue ;
}
if ( nh_dev ! = dev )
continue ;
alive + + ;
nh - > nh_flags & = ~ nh_flags ;
nh_flags & = ~ flags ;
WRITE_ONCE ( nh - > nh_flags , flags ) ;
} endfor_nexthops ( rt ) ;
ACCESS _ONCE( rt - > rt_nhn_alive ) = alive ;
WRITE _ONCE( rt - > rt_nhn_alive , alive ) ;
}
}
@ -1495,16 +1541,18 @@ int nla_put_labels(struct sk_buff *skb, int attrtype,
EXPORT_SYMBOL_GPL ( nla_put_labels ) ;
int nla_get_labels ( const struct nlattr * nla ,
u32 max_labels , u8 * labels , u32 label [ ] )
u8 max_labels , u8 * labels , u32 label [ ] )
{
unsigned len = nla_len ( nla ) ;
unsigned nla_labels ;
struct mpls_shim_hdr * nla_label ;
u8 nla_labels ;
bool bos ;
int i ;
/* len needs to be an even multiple of 4 (the label size) */
if ( len & 3 )
/* len needs to be an even multiple of 4 (the label size). Number
* of labels is a u8 so check for overflow .
*/
if ( len & 3 | | len / 4 > 255 )
return - EINVAL ;
/* Limit the number of new labels allowed */
@ -1512,6 +1560,10 @@ int nla_get_labels(const struct nlattr *nla,
if ( nla_labels > max_labels )
return - EINVAL ;
/* when label == NULL, caller wants number of labels */
if ( ! label )
goto out ;
nla_label = nla_data ( nla ) ;
bos = true ;
for ( i = nla_labels - 1 ; i > = 0 ; i - - , bos = false ) {
@ -1535,6 +1587,7 @@ int nla_get_labels(const struct nlattr *nla,
label [ i ] = dec . label ;
}
out :
* labels = nla_labels ;
return 0 ;
}
@ -1596,7 +1649,6 @@ static int rtm_to_route_config(struct sk_buff *skb, struct nlmsghdr *nlh,
err = - EINVAL ;
rtm = nlmsg_data ( nlh ) ;
memset ( cfg , 0 , sizeof ( * cfg ) ) ;
if ( rtm - > rtm_family ! = AF_MPLS )
goto errout ;
@ -1695,27 +1747,43 @@ errout:
static int mpls_rtm_delroute ( struct sk_buff * skb , struct nlmsghdr * nlh )
{
struct mpls_route_config cfg ;
struct mpls_route_config * cfg ;
int err ;
err = rtm_to_route_config ( skb , nlh , & cfg ) ;
cfg = kzalloc ( sizeof ( * cfg ) , GFP_KERNEL ) ;
if ( ! cfg )
return - ENOMEM ;
err = rtm_to_route_config ( skb , nlh , cfg ) ;
if ( err < 0 )
return err ;
goto out ;
err = mpls_route_del ( cfg ) ;
out :
kfree ( cfg ) ;
return mpls_route_del ( & cfg ) ;
return err ;
}
static int mpls_rtm_newroute ( struct sk_buff * skb , struct nlmsghdr * nlh )
{
struct mpls_route_config cfg ;
struct mpls_route_config * cfg ;
int err ;
err = rtm_to_route_config ( skb , nlh , & cfg ) ;
cfg = kzalloc ( sizeof ( * cfg ) , GFP_KERNEL ) ;
if ( ! cfg )
return - ENOMEM ;
err = rtm_to_route_config ( skb , nlh , cfg ) ;
if ( err < 0 )
return err ;
goto out ;
return mpls_route_add ( & cfg ) ;
err = mpls_route_add ( cfg ) ;
out :
kfree ( cfg ) ;
return err ;
}
static int mpls_dump_route ( struct sk_buff * skb , u32 portid , u32 seq , int event ,
@ -1772,8 +1840,8 @@ static int mpls_dump_route(struct sk_buff *skb, u32 portid, u32 seq, int event,
} else {
struct rtnexthop * rtnh ;
struct nlattr * mp ;
int dead = 0 ;
int linkdown = 0 ;
u8 linkdown = 0 ;
u8 dead = 0 ;
mp = nla_nest_start ( skb , RTA_MULTIPATH ) ;
if ( ! mp )
@ -1944,8 +2012,8 @@ static int resize_platform_label_table(struct net *net, size_t limit)
/* In case the predefined labels need to be populated */
if ( limit > MPLS_LABEL_IPV4NULL ) {
struct net_device * lo = net - > loopback_dev ;
rt0 = mpls_rt_alloc ( 1 , lo - > addr_len ) ;
if ( ! rt0 )
rt0 = mpls_rt_alloc ( 1 , lo - > addr_len , 0 ) ;
if ( IS_ERR ( rt0 ) )
goto nort0 ;
RCU_INIT_POINTER ( rt0 - > rt_nh - > nh_dev , lo ) ;
rt0 - > rt_protocol = RTPROT_KERNEL ;
@ -1958,8 +2026,8 @@ static int resize_platform_label_table(struct net *net, size_t limit)
}
if ( limit > MPLS_LABEL_IPV6NULL ) {
struct net_device * lo = net - > loopback_dev ;
rt2 = mpls_rt_alloc ( 1 , lo - > addr_len ) ;
if ( ! rt2 )
rt2 = mpls_rt_alloc ( 1 , lo - > addr_len , 0 ) ;
if ( IS_ERR ( rt2 ) )
goto nort2 ;
RCU_INIT_POINTER ( rt2 - > rt_nh - > nh_dev , lo ) ;
rt2 - > rt_protocol = RTPROT_KERNEL ;