2015-03-03 19:10:47 -06:00
# include <linux/types.h>
# include <linux/skbuff.h>
# include <linux/socket.h>
2015-03-03 19:11:20 -06:00
# include <linux/sysctl.h>
2015-03-03 19:10:47 -06:00
# include <linux/net.h>
# include <linux/module.h>
# include <linux/if_arp.h>
# include <linux/ipv6.h>
# include <linux/mpls.h>
2017-02-20 08:03:30 -08:00
# include <linux/netconf.h>
2015-03-05 13:37:05 +11:00
# include <linux/vmalloc.h>
2017-01-16 14:16:37 +00:00
# include <linux/percpu.h>
2015-03-03 19:10:47 -06:00
# include <net/ip.h>
# include <net/dst.h>
# include <net/sock.h>
# include <net/arp.h>
# include <net/ip_fib.h>
# include <net/netevent.h>
# include <net/netns/generic.h>
2015-07-30 13:34:54 -07:00
# if IS_ENABLED(CONFIG_IPV6)
# include <net/ipv6.h>
# endif
2017-01-16 14:16:37 +00:00
# include <net/addrconf.h>
2015-10-23 06:03:27 -07:00
# include <net/nexthop.h>
2015-03-03 19:10:47 -06:00
# include "internal.h"
2015-10-23 06:03:28 -07:00
/* Maximum number of labels to look ahead at when selecting a path of
* a multipath route
*/
# define MAX_MP_SELECT_LABELS 4
2015-12-10 19:30:50 +00:00
# define MPLS_NEIGH_TABLE_UNSPEC (NEIGH_LINK_TABLE + 1)
2015-03-03 19:11:20 -06:00
static int zero = 0 ;
2017-03-10 20:43:24 +00:00
static int one = 1 ;
2015-03-03 19:11:20 -06:00
static int label_limit = ( 1 < < 20 ) - 1 ;
2015-03-03 19:14:31 -06:00
static void rtmsg_lfib ( int event , u32 label , struct mpls_route * rt ,
struct nlmsghdr * nlh , struct net * net , u32 portid ,
unsigned int nlm_flags ) ;
2015-03-03 19:10:47 -06:00
static struct mpls_route * mpls_route_input_rcu ( struct net * net , unsigned index )
{
struct mpls_route * rt = NULL ;
if ( index < net - > mpls . platform_labels ) {
struct mpls_route __rcu * * platform_label =
rcu_dereference ( net - > mpls . platform_label ) ;
rt = rcu_dereference ( platform_label [ index ] ) ;
}
return rt ;
}
2015-07-21 10:43:52 +02:00
bool mpls_output_possible ( const struct net_device * dev )
2015-03-03 19:10:47 -06:00
{
return dev & & ( dev - > flags & IFF_UP ) & & netif_carrier_ok ( dev ) ;
}
2015-07-21 10:43:52 +02:00
EXPORT_SYMBOL_GPL ( mpls_output_possible ) ;
2015-03-03 19:10:47 -06:00
2015-10-27 00:37:36 +00:00
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 ;
}
static const u8 * mpls_nh_via ( const struct mpls_route * rt ,
const struct mpls_nh * nh )
{
return __mpls_nh_via ( ( struct mpls_route * ) rt , ( struct mpls_nh * ) nh ) ;
}
2015-10-23 06:03:27 -07:00
static unsigned int mpls_nh_header_size ( const struct mpls_nh * nh )
2015-03-03 19:10:47 -06:00
{
/* The size of the layer 2.5 labels to be added for this route */
2015-10-23 06:03:27 -07:00
return nh - > nh_labels * sizeof ( struct mpls_shim_hdr ) ;
2015-03-03 19:10:47 -06:00
}
2015-07-21 10:43:52 +02:00
unsigned int mpls_dev_mtu ( const struct net_device * dev )
2015-03-03 19:10:47 -06:00
{
/* The amount of data the layer 2 frame can hold */
return dev - > mtu ;
}
2015-07-21 10:43:52 +02:00
EXPORT_SYMBOL_GPL ( mpls_dev_mtu ) ;
2015-03-03 19:10:47 -06:00
2015-07-21 10:43:52 +02:00
bool mpls_pkt_too_big ( const struct sk_buff * skb , unsigned int mtu )
2015-03-03 19:10:47 -06:00
{
if ( skb - > len < = mtu )
return false ;
2016-06-02 15:05:41 -03:00
if ( skb_is_gso ( skb ) & & skb_gso_validate_mtu ( skb , mtu ) )
2015-03-03 19:10:47 -06:00
return false ;
return true ;
}
2015-07-21 10:43:52 +02:00
EXPORT_SYMBOL_GPL ( mpls_pkt_too_big ) ;
2015-03-03 19:10:47 -06:00
2017-01-16 14:16:37 +00:00
void mpls_stats_inc_outucastpkts ( struct net_device * dev ,
const struct sk_buff * skb )
{
struct mpls_dev * mdev ;
if ( skb - > protocol = = htons ( ETH_P_MPLS_UC ) ) {
mdev = mpls_dev_get ( dev ) ;
if ( mdev )
MPLS_INC_STATS_LEN ( mdev , skb - > len ,
tx_packets ,
tx_bytes ) ;
} else if ( skb - > protocol = = htons ( ETH_P_IP ) ) {
IP_UPD_PO_STATS ( dev_net ( dev ) , IPSTATS_MIB_OUT , skb - > len ) ;
# if IS_ENABLED(CONFIG_IPV6)
} else if ( skb - > protocol = = htons ( ETH_P_IPV6 ) ) {
struct inet6_dev * in6dev = __in6_dev_get ( dev ) ;
if ( in6dev )
IP6_UPD_PO_STATS ( dev_net ( dev ) , in6dev ,
IPSTATS_MIB_OUT , skb - > len ) ;
# endif
}
}
EXPORT_SYMBOL_GPL ( mpls_stats_inc_outucastpkts ) ;
2017-01-20 12:58:34 -08:00
static u32 mpls_multipath_hash ( struct mpls_route * rt , struct sk_buff * skb )
2015-10-23 06:03:27 -07:00
{
2015-10-23 06:03:28 -07:00
struct mpls_entry_decoded dec ;
2017-01-20 12:58:34 -08:00
unsigned int mpls_hdr_len = 0 ;
2015-10-23 06:03:28 -07:00
struct mpls_shim_hdr * hdr ;
bool eli_seen = false ;
int label_index ;
u32 hash = 0 ;
2017-01-20 12:58:34 -08:00
for ( label_index = 0 ; label_index < MAX_MP_SELECT_LABELS ;
2015-10-23 06:03:28 -07:00
label_index + + ) {
2017-01-20 12:58:34 -08:00
mpls_hdr_len + = sizeof ( * hdr ) ;
if ( ! pskb_may_pull ( skb , mpls_hdr_len ) )
2015-10-23 06:03:28 -07:00
break ;
/* Read and decode the current label */
hdr = mpls_hdr ( skb ) + label_index ;
dec = mpls_entry_decode ( hdr ) ;
/* RFC6790 - reserved labels MUST NOT be used as keys
* for the load - balancing function
*/
if ( likely ( dec . label > = MPLS_LABEL_FIRST_UNRESERVED ) ) {
hash = jhash_1word ( dec . label , hash ) ;
/* The entropy label follows the entropy label
* indicator , so this means that the entropy
* label was just added to the hash - no need to
* go any deeper either in the label stack or in the
* payload
*/
if ( eli_seen )
break ;
} else if ( dec . label = = MPLS_LABEL_ENTROPY ) {
eli_seen = true ;
}
2017-01-20 12:58:34 -08:00
if ( ! dec . bos )
continue ;
/* found bottom label; does skb have room for a header? */
if ( pskb_may_pull ( skb , mpls_hdr_len + sizeof ( struct iphdr ) ) ) {
2015-10-23 06:03:28 -07:00
const struct iphdr * v4hdr ;
2017-01-20 12:58:34 -08:00
v4hdr = ( const struct iphdr * ) ( hdr + 1 ) ;
2015-10-23 06:03:28 -07:00
if ( v4hdr - > version = = 4 ) {
hash = jhash_3words ( ntohl ( v4hdr - > saddr ) ,
ntohl ( v4hdr - > daddr ) ,
v4hdr - > protocol , hash ) ;
} else if ( v4hdr - > version = = 6 & &
2017-01-20 12:58:34 -08:00
pskb_may_pull ( skb , mpls_hdr_len +
sizeof ( struct ipv6hdr ) ) ) {
2015-10-23 06:03:28 -07:00
const struct ipv6hdr * v6hdr ;
2017-01-20 12:58:34 -08:00
v6hdr = ( const struct ipv6hdr * ) ( hdr + 1 ) ;
2015-10-23 06:03:28 -07:00
hash = __ipv6_addr_jhash ( & v6hdr - > saddr , hash ) ;
hash = __ipv6_addr_jhash ( & v6hdr - > daddr , hash ) ;
hash = jhash_1word ( v6hdr - > nexthdr , hash ) ;
}
}
2017-01-20 12:58:34 -08:00
break ;
2015-10-23 06:03:28 -07:00
}
2015-12-01 22:18:11 -08:00
return hash ;
}
static struct mpls_nh * mpls_select_multipath ( struct mpls_route * rt ,
2017-01-20 12:58:34 -08:00
struct sk_buff * skb )
2015-12-01 22:18:11 -08:00
{
int alive = ACCESS_ONCE ( rt - > rt_nhn_alive ) ;
u32 hash = 0 ;
int nh_index = 0 ;
int n = 0 ;
/* No need to look further into packet if there's only
* one path
*/
if ( rt - > rt_nhn = = 1 )
goto out ;
if ( alive < = 0 )
return NULL ;
2017-01-20 12:58:34 -08:00
hash = mpls_multipath_hash ( rt , skb ) ;
2015-12-01 22:18:11 -08:00
nh_index = hash % alive ;
if ( alive = = rt - > rt_nhn )
goto out ;
for_nexthops ( rt ) {
if ( nh - > nh_flags & ( RTNH_F_DEAD | RTNH_F_LINKDOWN ) )
continue ;
if ( n = = nh_index )
return nh ;
n + + ;
} endfor_nexthops ( rt ) ;
2015-10-23 06:03:28 -07:00
out :
return & rt - > rt_nh [ nh_index ] ;
2015-10-23 06:03:27 -07:00
}
2017-03-10 20:43:24 +00:00
static bool mpls_egress ( struct net * net , struct mpls_route * rt ,
struct sk_buff * skb , struct mpls_entry_decoded dec )
2015-03-03 19:10:47 -06:00
{
2015-08-06 11:04:56 +01:00
enum mpls_payload_type payload_type ;
bool success = false ;
2015-03-03 19:10:47 -06:00
2015-03-12 18:22:59 -05:00
/* The IPv4 code below accesses through the IPv4 header
* checksum , which is 12 bytes into the packet .
* The IPv6 code below accesses through the IPv6 hop limit
* which is 8 bytes into the packet .
*
* For all supported cases there should always be at least 12
* bytes of packet data present . The IPv4 header is 20 bytes
* without options and the IPv6 header is always 40 bytes
* long .
*/
if ( ! pskb_may_pull ( skb , 12 ) )
return false ;
2015-08-06 11:04:56 +01:00
payload_type = rt - > rt_payload_type ;
if ( payload_type = = MPT_UNSPEC )
payload_type = ip_hdr ( skb ) - > version ;
switch ( payload_type ) {
case MPT_IPV4 : {
struct iphdr * hdr4 = ip_hdr ( skb ) ;
2017-03-10 20:43:24 +00:00
u8 new_ttl ;
2015-03-03 19:10:47 -06:00
skb - > protocol = htons ( ETH_P_IP ) ;
2017-03-10 20:43:24 +00:00
/* If propagating TTL, take the decremented TTL from
* the incoming MPLS header , otherwise decrement the
* TTL , but only if not 0 to avoid underflow .
*/
if ( rt - > rt_ttl_propagate = = MPLS_TTL_PROP_ENABLED | |
( rt - > rt_ttl_propagate = = MPLS_TTL_PROP_DEFAULT & &
net - > mpls . ip_ttl_propagate ) )
new_ttl = dec . ttl ;
else
new_ttl = hdr4 - > ttl ? hdr4 - > ttl - 1 : 0 ;
2015-03-03 19:10:47 -06:00
csum_replace2 ( & hdr4 - > check ,
htons ( hdr4 - > ttl < < 8 ) ,
2017-03-10 20:43:24 +00:00
htons ( new_ttl < < 8 ) ) ;
hdr4 - > ttl = new_ttl ;
2015-08-06 11:04:56 +01:00
success = true ;
break ;
2015-03-03 19:10:47 -06:00
}
2015-08-06 11:04:56 +01:00
case MPT_IPV6 : {
2015-03-03 19:10:47 -06:00
struct ipv6hdr * hdr6 = ipv6_hdr ( skb ) ;
skb - > protocol = htons ( ETH_P_IPV6 ) ;
2017-03-10 20:43:24 +00:00
/* If propagating TTL, take the decremented TTL from
* the incoming MPLS header , otherwise decrement the
* hop limit , but only if not 0 to avoid underflow .
*/
if ( rt - > rt_ttl_propagate = = MPLS_TTL_PROP_ENABLED | |
( rt - > rt_ttl_propagate = = MPLS_TTL_PROP_DEFAULT & &
net - > mpls . ip_ttl_propagate ) )
hdr6 - > hop_limit = dec . ttl ;
else if ( hdr6 - > hop_limit )
hdr6 - > hop_limit = hdr6 - > hop_limit - 1 ;
2015-08-06 11:04:56 +01:00
success = true ;
break ;
2015-03-03 19:10:47 -06:00
}
2015-08-06 11:04:56 +01:00
case MPT_UNSPEC :
2017-03-10 20:43:24 +00:00
/* Should have decided which protocol it is by now */
2015-08-06 11:04:56 +01:00
break ;
}
2015-03-03 19:10:47 -06:00
return success ;
}
static int mpls_forward ( struct sk_buff * skb , struct net_device * dev ,
struct packet_type * pt , struct net_device * orig_dev )
{
struct net * net = dev_net ( dev ) ;
struct mpls_shim_hdr * hdr ;
struct mpls_route * rt ;
2015-10-23 06:03:27 -07:00
struct mpls_nh * nh ;
2015-03-03 19:10:47 -06:00
struct mpls_entry_decoded dec ;
struct net_device * out_dev ;
2017-01-16 14:16:37 +00:00
struct mpls_dev * out_mdev ;
2015-04-22 11:14:37 +01:00
struct mpls_dev * mdev ;
2015-03-03 19:10:47 -06:00
unsigned int hh_len ;
unsigned int new_header_size ;
unsigned int mtu ;
int err ;
/* Careful this entire function runs inside of an rcu critical section */
2015-04-22 11:14:37 +01:00
mdev = mpls_dev_get ( dev ) ;
2017-01-16 14:16:37 +00:00
if ( ! mdev )
2015-04-22 11:14:37 +01:00
goto drop ;
2017-01-16 14:16:37 +00:00
MPLS_INC_STATS_LEN ( mdev , skb - > len , rx_packets ,
rx_bytes ) ;
if ( ! mdev - > input_enabled ) {
MPLS_INC_STATS ( mdev , rx_dropped ) ;
2015-03-03 19:10:47 -06:00
goto drop ;
2017-01-16 14:16:37 +00:00
}
if ( skb - > pkt_type ! = PACKET_HOST )
goto err ;
2015-03-03 19:10:47 -06:00
if ( ( skb = skb_share_check ( skb , GFP_ATOMIC ) ) = = NULL )
2017-01-16 14:16:37 +00:00
goto err ;
2015-03-03 19:10:47 -06:00
if ( ! pskb_may_pull ( skb , sizeof ( * hdr ) ) )
2017-01-16 14:16:37 +00:00
goto err ;
2015-03-03 19:10:47 -06:00
/* Read and decode the label */
hdr = mpls_hdr ( skb ) ;
dec = mpls_entry_decode ( hdr ) ;
rt = mpls_route_input_rcu ( net , dec . label ) ;
2017-01-16 14:16:37 +00:00
if ( ! rt ) {
MPLS_INC_STATS ( mdev , rx_noroute ) ;
2015-03-03 19:10:47 -06:00
goto drop ;
2017-01-16 14:16:37 +00:00
}
2015-03-03 19:10:47 -06:00
2017-01-20 12:58:34 -08:00
nh = mpls_select_multipath ( rt , skb ) ;
2015-10-23 06:03:27 -07:00
if ( ! nh )
2017-01-16 14:16:37 +00:00
goto err ;
2015-03-03 19:10:47 -06:00
2017-01-20 12:58:34 -08:00
/* Pop the label */
skb_pull ( skb , sizeof ( * hdr ) ) ;
skb_reset_network_header ( skb ) ;
skb_orphan ( skb ) ;
2015-03-03 19:10:47 -06:00
if ( skb_warn_if_lro ( skb ) )
2017-01-16 14:16:37 +00:00
goto err ;
2015-03-03 19:10:47 -06:00
skb_forward_csum ( skb ) ;
/* Verify ttl is valid */
2015-03-07 16:23:23 -06:00
if ( dec . ttl < = 1 )
2017-01-16 14:16:37 +00:00
goto err ;
2015-03-03 19:10:47 -06:00
dec . ttl - = 1 ;
2017-01-16 14:16:37 +00:00
/* Find the output device */
out_dev = rcu_dereference ( nh - > nh_dev ) ;
if ( ! mpls_output_possible ( out_dev ) )
goto tx_err ;
2015-03-03 19:10:47 -06:00
/* Verify the destination can hold the packet */
2015-10-23 06:03:27 -07:00
new_header_size = mpls_nh_header_size ( nh ) ;
2015-03-03 19:10:47 -06:00
mtu = mpls_dev_mtu ( out_dev ) ;
if ( mpls_pkt_too_big ( skb , mtu - new_header_size ) )
2017-01-16 14:16:37 +00:00
goto tx_err ;
2015-03-03 19:10:47 -06:00
hh_len = LL_RESERVED_SPACE ( out_dev ) ;
if ( ! out_dev - > header_ops )
hh_len = 0 ;
/* Ensure there is enough space for the headers in the skb */
if ( skb_cow ( skb , hh_len + new_header_size ) )
2017-01-16 14:16:37 +00:00
goto tx_err ;
2015-03-03 19:10:47 -06:00
skb - > dev = out_dev ;
skb - > protocol = htons ( ETH_P_MPLS_UC ) ;
if ( unlikely ( ! new_header_size & & dec . bos ) ) {
/* Penultimate hop popping */
2017-03-10 20:43:24 +00:00
if ( ! mpls_egress ( dev_net ( out_dev ) , rt , skb , dec ) )
2017-01-16 14:16:37 +00:00
goto err ;
2015-03-03 19:10:47 -06:00
} else {
bool bos ;
int i ;
skb_push ( skb , new_header_size ) ;
skb_reset_network_header ( skb ) ;
/* Push the new labels */
hdr = mpls_hdr ( skb ) ;
bos = dec . bos ;
2015-10-23 06:03:27 -07:00
for ( i = nh - > nh_labels - 1 ; i > = 0 ; i - - ) {
hdr [ i ] = mpls_entry_encode ( nh - > nh_label [ i ] ,
dec . ttl , 0 , bos ) ;
2015-03-03 19:10:47 -06:00
bos = false ;
}
}
2017-01-16 14:16:37 +00:00
mpls_stats_inc_outucastpkts ( out_dev , skb ) ;
2015-12-10 19:30:50 +00:00
/* If via wasn't specified then send out using device address */
if ( nh - > nh_via_table = = MPLS_NEIGH_TABLE_UNSPEC )
err = neigh_xmit ( NEIGH_LINK_TABLE , out_dev ,
out_dev - > dev_addr , skb ) ;
else
err = neigh_xmit ( nh - > nh_via_table , out_dev ,
mpls_nh_via ( rt , nh ) , skb ) ;
2015-03-03 19:10:47 -06:00
if ( err )
net_dbg_ratelimited ( " %s: packet transmission failed: %d \n " ,
__func__ , err ) ;
return 0 ;
2017-01-16 14:16:37 +00:00
tx_err :
out_mdev = out_dev ? mpls_dev_get ( out_dev ) : NULL ;
if ( out_mdev )
MPLS_INC_STATS ( out_mdev , tx_errors ) ;
goto drop ;
err :
MPLS_INC_STATS ( mdev , rx_errors ) ;
2015-03-03 19:10:47 -06:00
drop :
kfree_skb ( skb ) ;
return NET_RX_DROP ;
}
static struct packet_type mpls_packet_type __read_mostly = {
. type = cpu_to_be16 ( ETH_P_MPLS_UC ) ,
. func = mpls_forward ,
} ;
2015-03-05 05:33:54 +08:00
static const struct nla_policy rtm_mpls_policy [ RTA_MAX + 1 ] = {
2015-03-03 19:13:56 -06:00
[ RTA_DST ] = { . type = NLA_U32 } ,
[ RTA_OIF ] = { . type = NLA_U32 } ,
2017-03-10 20:43:24 +00:00
[ RTA_TTL_PROPAGATE ] = { . type = NLA_U8 } ,
2015-03-03 19:13:56 -06:00
} ;
2015-03-03 19:12:40 -06:00
struct mpls_route_config {
2015-08-06 11:04:56 +01:00
u32 rc_protocol ;
u32 rc_ifindex ;
2015-10-23 06:03:27 -07:00
u8 rc_via_table ;
u8 rc_via_alen ;
2015-08-06 11:04:56 +01:00
u8 rc_via [ MAX_VIA_ALEN ] ;
u32 rc_label ;
2017-03-10 20:43:24 +00:00
u8 rc_ttl_propagate ;
2015-10-23 06:03:27 -07:00
u8 rc_output_labels ;
2015-08-06 11:04:56 +01:00
u32 rc_output_label [ MAX_NEW_LABELS ] ;
u32 rc_nlflags ;
enum mpls_payload_type rc_payload_type ;
struct nl_info rc_nlinfo ;
2015-10-23 06:03:27 -07:00
struct rtnexthop * rc_mp ;
int rc_mp_len ;
2015-03-03 19:12:40 -06:00
} ;
2015-10-27 00:37:36 +00:00
static struct mpls_route * mpls_rt_alloc ( int num_nh , u8 max_alen )
2015-03-03 19:10:47 -06:00
{
2015-10-27 00:37:36 +00:00
u8 max_alen_aligned = ALIGN ( max_alen , VIA_ALEN_ALIGN ) ;
2015-03-03 19:10:47 -06:00
struct mpls_route * rt ;
2015-10-27 00:37:36 +00:00
rt = kzalloc ( ALIGN ( sizeof ( * rt ) + num_nh * sizeof ( * rt - > rt_nh ) ,
VIA_ALEN_ALIGN ) +
num_nh * max_alen_aligned ,
2015-10-23 06:03:27 -07:00
GFP_KERNEL ) ;
2015-10-27 00:37:36 +00:00
if ( rt ) {
2015-10-23 06:03:27 -07:00
rt - > rt_nhn = num_nh ;
2015-12-01 22:18:11 -08:00
rt - > rt_nhn_alive = num_nh ;
2015-10-27 00:37:36 +00:00
rt - > rt_max_alen = max_alen_aligned ;
}
2015-10-23 06:03:27 -07:00
2015-03-03 19:10:47 -06:00
return rt ;
}
static void mpls_rt_free ( struct mpls_route * rt )
{
if ( rt )
kfree_rcu ( rt , rt_rcu ) ;
}
2015-03-03 19:14:31 -06:00
static void mpls_notify_route ( struct net * net , unsigned index ,
struct mpls_route * old , struct mpls_route * new ,
const struct nl_info * info )
{
struct nlmsghdr * nlh = info ? info - > nlh : NULL ;
unsigned portid = info ? info - > portid : 0 ;
int event = new ? RTM_NEWROUTE : RTM_DELROUTE ;
struct mpls_route * rt = new ? new : old ;
unsigned nlm_flags = ( old & & new ) ? NLM_F_REPLACE : 0 ;
/* Ignore reserved labels for now */
2015-08-03 17:50:04 +01:00
if ( rt & & ( index > = MPLS_LABEL_FIRST_UNRESERVED ) )
2015-03-03 19:14:31 -06:00
rtmsg_lfib ( event , index , rt , nlh , net , portid , nlm_flags ) ;
}
2015-03-03 19:10:47 -06:00
static void mpls_route_update ( struct net * net , unsigned index ,
2015-10-23 06:03:27 -07:00
struct mpls_route * new ,
2015-03-03 19:10:47 -06:00
const struct nl_info * info )
{
2015-03-07 16:21:56 -06:00
struct mpls_route __rcu * * platform_label ;
2015-10-23 06:03:27 -07:00
struct mpls_route * rt ;
2015-03-03 19:10:47 -06:00
ASSERT_RTNL ( ) ;
2015-03-07 16:21:56 -06:00
platform_label = rtnl_dereference ( net - > mpls . platform_label ) ;
rt = rtnl_dereference ( platform_label [ index ] ) ;
2015-10-23 06:03:27 -07:00
rcu_assign_pointer ( platform_label [ index ] , new ) ;
2015-03-03 19:10:47 -06:00
2015-10-23 06:03:27 -07:00
mpls_notify_route ( net , index , rt , new , info ) ;
2015-03-03 19:14:31 -06:00
2015-03-03 19:10:47 -06:00
/* If we removed a route free it now */
2015-10-23 06:03:27 -07:00
mpls_rt_free ( rt ) ;
2015-03-03 19:10:47 -06:00
}
2015-03-03 19:12:40 -06:00
static unsigned find_free_label ( struct net * net )
{
2015-03-07 16:21:56 -06:00
struct mpls_route __rcu * * platform_label ;
size_t platform_labels ;
2015-03-03 19:12:40 -06:00
unsigned index ;
2015-03-07 16:21:56 -06:00
platform_label = rtnl_dereference ( net - > mpls . platform_label ) ;
platform_labels = net - > mpls . platform_labels ;
2015-08-03 17:50:04 +01:00
for ( index = MPLS_LABEL_FIRST_UNRESERVED ; index < platform_labels ;
index + + ) {
2015-03-07 16:21:56 -06:00
if ( ! rtnl_dereference ( platform_label [ index ] ) )
2015-03-03 19:12:40 -06:00
return index ;
}
return LABEL_NOT_SPECIFIED ;
}
2015-07-30 13:34:54 -07:00
# if IS_ENABLED(CONFIG_INET)
2015-10-27 00:37:36 +00:00
static struct net_device * inet_fib_lookup_dev ( struct net * net ,
const void * addr )
2015-07-21 09:16:24 -07:00
{
2015-08-04 10:44:22 +03:00
struct net_device * dev ;
2015-07-21 09:16:24 -07:00
struct rtable * rt ;
struct in_addr daddr ;
memcpy ( & daddr , addr , sizeof ( struct in_addr ) ) ;
rt = ip_route_output ( net , daddr . s_addr , 0 , 0 , 0 ) ;
if ( IS_ERR ( rt ) )
2015-08-04 10:44:22 +03:00
return ERR_CAST ( rt ) ;
2015-07-21 09:16:24 -07:00
dev = rt - > dst . dev ;
dev_hold ( dev ) ;
ip_rt_put ( rt ) ;
return dev ;
2015-07-30 13:34:54 -07:00
}
# else
2015-10-27 00:37:36 +00:00
static struct net_device * inet_fib_lookup_dev ( struct net * net ,
const void * addr )
2015-07-30 13:34:54 -07:00
{
return ERR_PTR ( - EAFNOSUPPORT ) ;
2015-07-21 09:16:24 -07:00
}
2015-07-30 13:34:54 -07:00
# endif
2015-07-21 09:16:24 -07:00
2015-07-30 13:34:54 -07:00
# if IS_ENABLED(CONFIG_IPV6)
2015-10-27 00:37:36 +00:00
static struct net_device * inet6_fib_lookup_dev ( struct net * net ,
const void * addr )
2015-07-21 09:16:24 -07:00
{
2015-08-04 10:44:22 +03:00
struct net_device * dev ;
2015-07-21 09:16:24 -07:00
struct dst_entry * dst ;
struct flowi6 fl6 ;
2015-07-30 13:34:54 -07:00
int err ;
if ( ! ipv6_stub )
return ERR_PTR ( - EAFNOSUPPORT ) ;
2015-07-21 09:16:24 -07:00
memset ( & fl6 , 0 , sizeof ( fl6 ) ) ;
memcpy ( & fl6 . daddr , addr , sizeof ( struct in6_addr ) ) ;
2015-07-30 13:34:54 -07:00
err = ipv6_stub - > ipv6_dst_lookup ( net , NULL , & dst , & fl6 ) ;
if ( err )
2015-08-04 10:44:22 +03:00
return ERR_PTR ( err ) ;
2015-07-21 09:16:24 -07:00
dev = dst - > dev ;
dev_hold ( dev ) ;
dst_release ( dst ) ;
return dev ;
}
2015-07-30 13:34:54 -07:00
# else
2015-10-27 00:37:36 +00:00
static struct net_device * inet6_fib_lookup_dev ( struct net * net ,
const void * addr )
2015-07-30 13:34:54 -07:00
{
return ERR_PTR ( - EAFNOSUPPORT ) ;
}
# endif
2015-07-21 09:16:24 -07:00
static struct net_device * find_outdev ( struct net * net ,
2015-10-27 00:37:36 +00:00
struct mpls_route * rt ,
2015-10-23 06:03:27 -07:00
struct mpls_nh * nh , int oif )
2015-07-21 09:16:24 -07:00
{
struct net_device * dev = NULL ;
2015-10-23 06:03:27 -07:00
if ( ! oif ) {
switch ( nh - > nh_via_table ) {
2015-07-21 09:16:24 -07:00
case NEIGH_ARP_TABLE :
2015-10-27 00:37:36 +00:00
dev = inet_fib_lookup_dev ( net , mpls_nh_via ( rt , nh ) ) ;
2015-07-21 09:16:24 -07:00
break ;
case NEIGH_ND_TABLE :
2015-10-27 00:37:36 +00:00
dev = inet6_fib_lookup_dev ( net , mpls_nh_via ( rt , nh ) ) ;
2015-07-21 09:16:24 -07:00
break ;
case NEIGH_LINK_TABLE :
break ;
}
} else {
2015-10-23 06:03:27 -07:00
dev = dev_get_by_index ( net , oif ) ;
2015-07-21 09:16:24 -07:00
}
2015-08-04 06:36:24 -07:00
if ( ! dev )
return ERR_PTR ( - ENODEV ) ;
2016-04-07 21:28:38 -07:00
if ( IS_ERR ( dev ) )
return dev ;
2015-10-23 06:03:27 -07:00
/* The caller is holding rtnl anyways, so release the dev reference */
dev_put ( dev ) ;
2015-07-21 09:16:24 -07:00
return dev ;
}
2015-10-27 00:37:36 +00:00
static int mpls_nh_assign_dev ( struct net * net , struct mpls_route * rt ,
struct mpls_nh * nh , int oif )
2015-10-23 06:03:27 -07:00
{
struct net_device * dev = NULL ;
int err = - ENODEV ;
2015-10-27 00:37:36 +00:00
dev = find_outdev ( net , rt , nh , oif ) ;
2015-10-23 06:03:27 -07:00
if ( IS_ERR ( dev ) ) {
err = PTR_ERR ( dev ) ;
dev = NULL ;
goto errout ;
}
/* Ensure this is a supported device */
err = - EINVAL ;
if ( ! mpls_dev_get ( dev ) )
goto errout ;
2015-12-10 19:30:48 +00:00
if ( ( nh - > nh_via_table = = NEIGH_LINK_TABLE ) & &
( dev - > addr_len ! = nh - > nh_via_alen ) )
goto errout ;
2015-10-23 06:03:27 -07:00
RCU_INIT_POINTER ( nh - > nh_dev , dev ) ;
2015-12-01 22:18:11 -08:00
if ( ! ( dev - > flags & IFF_UP ) ) {
nh - > nh_flags | = RTNH_F_DEAD ;
} else {
unsigned int flags ;
flags = dev_get_flags ( dev ) ;
if ( ! ( flags & ( IFF_RUNNING | IFF_LOWER_UP ) ) )
nh - > nh_flags | = RTNH_F_LINKDOWN ;
}
2015-10-23 06:03:27 -07:00
return 0 ;
errout :
return err ;
}
static int mpls_nh_build_from_cfg ( struct mpls_route_config * cfg ,
struct mpls_route * rt )
{
struct net * net = cfg - > rc_nlinfo . nl_net ;
struct mpls_nh * nh = rt - > rt_nh ;
int err ;
int i ;
if ( ! nh )
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 + + )
nh - > nh_label [ i ] = cfg - > rc_output_label [ i ] ;
nh - > nh_via_table = cfg - > rc_via_table ;
2015-10-27 00:37:36 +00:00
memcpy ( __mpls_nh_via ( rt , nh ) , cfg - > rc_via , cfg - > rc_via_alen ) ;
2015-10-23 06:03:27 -07:00
nh - > nh_via_alen = cfg - > rc_via_alen ;
2015-10-27 00:37:36 +00:00
err = mpls_nh_assign_dev ( net , rt , nh , cfg - > rc_ifindex ) ;
2015-10-23 06:03:27 -07:00
if ( err )
goto errout ;
2015-12-01 22:18:11 -08:00
if ( nh - > nh_flags & ( RTNH_F_DEAD | RTNH_F_LINKDOWN ) )
rt - > rt_nhn_alive - - ;
2015-10-23 06:03:27 -07:00
return 0 ;
errout :
return err ;
}
2015-10-27 00:37:36 +00:00
static int mpls_nh_build ( struct net * net , struct mpls_route * rt ,
2015-12-01 22:18:11 -08:00
struct mpls_nh * nh , int oif , struct nlattr * via ,
struct nlattr * newdst )
2015-10-23 06:03:27 -07:00
{
int err = - ENOMEM ;
if ( ! nh )
goto errout ;
if ( newdst ) {
err = nla_get_labels ( newdst , MAX_NEW_LABELS ,
& nh - > nh_labels , nh - > nh_label ) ;
if ( err )
goto errout ;
}
2015-12-10 19:30:51 +00:00
if ( via ) {
err = nla_get_via ( via , & nh - > nh_via_alen , & nh - > nh_via_table ,
__mpls_nh_via ( rt , nh ) ) ;
if ( err )
goto errout ;
} else {
nh - > nh_via_table = MPLS_NEIGH_TABLE_UNSPEC ;
}
2015-10-23 06:03:27 -07:00
2015-10-27 00:37:36 +00:00
err = mpls_nh_assign_dev ( net , rt , nh , oif ) ;
2015-10-23 06:03:27 -07:00
if ( err )
goto errout ;
return 0 ;
errout :
return err ;
}
2015-10-27 00:37:36 +00:00
static int mpls_count_nexthops ( struct rtnexthop * rtnh , int len ,
u8 cfg_via_alen , u8 * max_via_alen )
2015-10-23 06:03:27 -07:00
{
int nhs = 0 ;
int remaining = len ;
2015-10-27 00:37:36 +00:00
if ( ! rtnh ) {
* max_via_alen = cfg_via_alen ;
return 1 ;
}
* max_via_alen = 0 ;
2015-10-23 06:03:27 -07:00
while ( rtnh_ok ( rtnh , remaining ) ) {
2015-10-27 00:37:36 +00:00
struct nlattr * nla , * attrs = rtnh_attrs ( rtnh ) ;
int attrlen ;
attrlen = rtnh_attrlen ( rtnh ) ;
nla = nla_find ( attrs , attrlen , RTA_VIA ) ;
if ( nla & & nla_len ( nla ) > =
offsetof ( struct rtvia , rtvia_addr ) ) {
int via_alen = nla_len ( nla ) -
offsetof ( struct rtvia , rtvia_addr ) ;
if ( via_alen < = MAX_VIA_ALEN )
* max_via_alen = max_t ( u16 , * max_via_alen ,
via_alen ) ;
}
2015-10-23 06:03:27 -07:00
nhs + + ;
rtnh = rtnh_next ( rtnh , & remaining ) ;
}
/* leftover implies invalid nexthop configuration, discard it */
return remaining > 0 ? 0 : nhs ;
}
static int mpls_nh_build_multi ( struct mpls_route_config * cfg ,
struct mpls_route * rt )
{
struct rtnexthop * rtnh = cfg - > rc_mp ;
struct nlattr * nla_via , * nla_newdst ;
int remaining = cfg - > rc_mp_len ;
int nhs = 0 ;
int err = 0 ;
change_nexthops ( rt ) {
int attrlen ;
nla_via = NULL ;
nla_newdst = NULL ;
err = - EINVAL ;
if ( ! rtnh_ok ( rtnh , remaining ) )
goto errout ;
2015-10-23 06:03:28 -07:00
/* neither weighted multipath nor any flags
* are supported
*/
if ( rtnh - > rtnh_hops | | rtnh - > rtnh_flags )
goto errout ;
2015-10-23 06:03:27 -07:00
attrlen = rtnh_attrlen ( rtnh ) ;
if ( attrlen > 0 ) {
struct nlattr * attrs = rtnh_attrs ( rtnh ) ;
nla_via = nla_find ( attrs , attrlen , RTA_VIA ) ;
nla_newdst = nla_find ( attrs , attrlen , RTA_NEWDST ) ;
}
2015-10-27 00:37:36 +00:00
err = mpls_nh_build ( cfg - > rc_nlinfo . nl_net , rt , nh ,
2015-12-01 22:18:11 -08:00
rtnh - > rtnh_ifindex , nla_via , nla_newdst ) ;
2015-10-23 06:03:27 -07:00
if ( err )
goto errout ;
2015-12-01 22:18:11 -08:00
if ( nh - > nh_flags & ( RTNH_F_DEAD | RTNH_F_LINKDOWN ) )
rt - > rt_nhn_alive - - ;
2015-10-23 06:03:27 -07:00
rtnh = rtnh_next ( rtnh , & remaining ) ;
nhs + + ;
} endfor_nexthops ( rt ) ;
rt - > rt_nhn = nhs ;
return 0 ;
errout :
return err ;
}
2015-03-03 19:12:40 -06:00
static int mpls_route_add ( struct mpls_route_config * cfg )
{
2015-03-07 16:21:56 -06:00
struct mpls_route __rcu * * platform_label ;
2015-03-03 19:12:40 -06:00
struct net * net = cfg - > rc_nlinfo . nl_net ;
struct mpls_route * rt , * old ;
int err = - EINVAL ;
2015-10-27 00:37:36 +00:00
u8 max_via_alen ;
2015-10-23 06:03:27 -07:00
unsigned index ;
2015-10-27 00:37:36 +00:00
int nhs ;
2015-03-03 19:12:40 -06:00
index = cfg - > rc_label ;
/* If a label was not specified during insert pick one */
if ( ( index = = LABEL_NOT_SPECIFIED ) & &
( cfg - > rc_nlflags & NLM_F_CREATE ) ) {
index = find_free_label ( net ) ;
}
2015-08-03 17:50:04 +01:00
/* Reserved labels may not be set */
if ( index < MPLS_LABEL_FIRST_UNRESERVED )
2015-03-03 19:12:40 -06:00
goto errout ;
/* The full 20 bit range may not be supported. */
if ( index > = net - > mpls . platform_labels )
goto errout ;
/* Append makes no sense with mpls */
2015-03-07 16:22:40 -06:00
err = - EOPNOTSUPP ;
2015-03-03 19:12:40 -06:00
if ( cfg - > rc_nlflags & NLM_F_APPEND )
goto errout ;
err = - EEXIST ;
2015-03-07 16:21:56 -06:00
platform_label = rtnl_dereference ( net - > mpls . platform_label ) ;
old = rtnl_dereference ( platform_label [ index ] ) ;
2015-03-03 19:12:40 -06:00
if ( ( cfg - > rc_nlflags & NLM_F_EXCL ) & & old )
goto errout ;
err = - EEXIST ;
if ( ! ( cfg - > rc_nlflags & NLM_F_REPLACE ) & & old )
goto errout ;
err = - ENOENT ;
if ( ! ( cfg - > rc_nlflags & NLM_F_CREATE ) & & ! old )
goto errout ;
2015-10-27 00:37:36 +00:00
err = - EINVAL ;
nhs = mpls_count_nexthops ( cfg - > rc_mp , cfg - > rc_mp_len ,
cfg - > rc_via_alen , & max_via_alen ) ;
if ( nhs = = 0 )
goto errout ;
2015-10-23 06:03:27 -07:00
2015-03-03 19:12:40 -06:00
err = - ENOMEM ;
2015-10-27 00:37:36 +00:00
rt = mpls_rt_alloc ( nhs , max_via_alen ) ;
2015-03-03 19:12:40 -06:00
if ( ! rt )
goto errout ;
rt - > rt_protocol = cfg - > rc_protocol ;
2015-08-06 11:04:56 +01:00
rt - > rt_payload_type = cfg - > rc_payload_type ;
2017-03-10 20:43:24 +00:00
rt - > rt_ttl_propagate = cfg - > rc_ttl_propagate ;
2015-03-03 19:12:40 -06:00
2015-10-23 06:03:27 -07:00
if ( cfg - > rc_mp )
err = mpls_nh_build_multi ( cfg , rt ) ;
else
err = mpls_nh_build_from_cfg ( cfg , rt ) ;
if ( err )
goto freert ;
mpls_route_update ( net , index , rt , & cfg - > rc_nlinfo ) ;
2015-03-03 19:12:40 -06:00
return 0 ;
2015-10-23 06:03:27 -07:00
freert :
mpls_rt_free ( rt ) ;
2015-03-03 19:12:40 -06:00
errout :
return err ;
}
static int mpls_route_del ( struct mpls_route_config * cfg )
{
struct net * net = cfg - > rc_nlinfo . nl_net ;
unsigned index ;
int err = - EINVAL ;
index = cfg - > rc_label ;
2015-08-03 17:50:04 +01:00
/* Reserved labels may not be removed */
if ( index < MPLS_LABEL_FIRST_UNRESERVED )
2015-03-03 19:12:40 -06:00
goto errout ;
/* The full 20 bit range may not be supported */
if ( index > = net - > mpls . platform_labels )
goto errout ;
2015-10-23 06:03:27 -07:00
mpls_route_update ( net , index , NULL , & cfg - > rc_nlinfo ) ;
2015-03-03 19:12:40 -06:00
err = 0 ;
errout :
return err ;
}
2017-01-16 14:16:37 +00:00
static void mpls_get_stats ( struct mpls_dev * mdev ,
struct mpls_link_stats * stats )
{
struct mpls_pcpu_stats * p ;
int i ;
memset ( stats , 0 , sizeof ( * stats ) ) ;
for_each_possible_cpu ( i ) {
struct mpls_link_stats local ;
unsigned int start ;
p = per_cpu_ptr ( mdev - > stats , i ) ;
do {
start = u64_stats_fetch_begin ( & p - > syncp ) ;
local = p - > stats ;
} while ( u64_stats_fetch_retry ( & p - > syncp , start ) ) ;
stats - > rx_packets + = local . rx_packets ;
stats - > rx_bytes + = local . rx_bytes ;
stats - > tx_packets + = local . tx_packets ;
stats - > tx_bytes + = local . tx_bytes ;
stats - > rx_errors + = local . rx_errors ;
stats - > tx_errors + = local . tx_errors ;
stats - > rx_dropped + = local . rx_dropped ;
stats - > tx_dropped + = local . tx_dropped ;
stats - > rx_noroute + = local . rx_noroute ;
}
}
static int mpls_fill_stats_af ( struct sk_buff * skb ,
const struct net_device * dev )
{
struct mpls_link_stats * stats ;
struct mpls_dev * mdev ;
struct nlattr * nla ;
mdev = mpls_dev_get ( dev ) ;
if ( ! mdev )
return - ENODATA ;
nla = nla_reserve_64bit ( skb , MPLS_STATS_LINK ,
sizeof ( struct mpls_link_stats ) ,
MPLS_STATS_UNSPEC ) ;
if ( ! nla )
return - EMSGSIZE ;
stats = nla_data ( nla ) ;
mpls_get_stats ( mdev , stats ) ;
return 0 ;
}
static size_t mpls_get_stats_af_size ( const struct net_device * dev )
{
struct mpls_dev * mdev ;
mdev = mpls_dev_get ( dev ) ;
if ( ! mdev )
return 0 ;
return nla_total_size_64bit ( sizeof ( struct mpls_link_stats ) ) ;
}
2017-02-20 08:03:30 -08:00
static int mpls_netconf_fill_devconf ( struct sk_buff * skb , struct mpls_dev * mdev ,
u32 portid , u32 seq , int event ,
unsigned int flags , int type )
{
struct nlmsghdr * nlh ;
struct netconfmsg * ncm ;
bool all = false ;
nlh = nlmsg_put ( skb , portid , seq , event , sizeof ( struct netconfmsg ) ,
flags ) ;
if ( ! nlh )
return - EMSGSIZE ;
if ( type = = NETCONFA_ALL )
all = true ;
ncm = nlmsg_data ( nlh ) ;
ncm - > ncm_family = AF_MPLS ;
if ( nla_put_s32 ( skb , NETCONFA_IFINDEX , mdev - > dev - > ifindex ) < 0 )
goto nla_put_failure ;
if ( ( all | | type = = NETCONFA_INPUT ) & &
nla_put_s32 ( skb , NETCONFA_INPUT ,
mdev - > input_enabled ) < 0 )
goto nla_put_failure ;
nlmsg_end ( skb , nlh ) ;
return 0 ;
nla_put_failure :
nlmsg_cancel ( skb , nlh ) ;
return - EMSGSIZE ;
}
static int mpls_netconf_msgsize_devconf ( int type )
{
int size = NLMSG_ALIGN ( sizeof ( struct netconfmsg ) )
+ nla_total_size ( 4 ) ; /* NETCONFA_IFINDEX */
bool all = false ;
if ( type = = NETCONFA_ALL )
all = true ;
if ( all | | type = = NETCONFA_INPUT )
size + = nla_total_size ( 4 ) ;
return size ;
}
static void mpls_netconf_notify_devconf ( struct net * net , int type ,
struct mpls_dev * mdev )
{
struct sk_buff * skb ;
int err = - ENOBUFS ;
skb = nlmsg_new ( mpls_netconf_msgsize_devconf ( type ) , GFP_KERNEL ) ;
if ( ! skb )
goto errout ;
err = mpls_netconf_fill_devconf ( skb , mdev , 0 , 0 , RTM_NEWNETCONF ,
0 , type ) ;
if ( err < 0 ) {
/* -EMSGSIZE implies BUG in mpls_netconf_msgsize_devconf() */
WARN_ON ( err = = - EMSGSIZE ) ;
kfree_skb ( skb ) ;
goto errout ;
}
rtnl_notify ( skb , net , 0 , RTNLGRP_MPLS_NETCONF , NULL , GFP_KERNEL ) ;
return ;
errout :
if ( err < 0 )
rtnl_set_sk_err ( net , RTNLGRP_MPLS_NETCONF , err ) ;
}
static const struct nla_policy devconf_mpls_policy [ NETCONFA_MAX + 1 ] = {
[ NETCONFA_IFINDEX ] = { . len = sizeof ( int ) } ,
} ;
static int mpls_netconf_get_devconf ( struct sk_buff * in_skb ,
struct nlmsghdr * nlh )
{
struct net * net = sock_net ( in_skb - > sk ) ;
struct nlattr * tb [ NETCONFA_MAX + 1 ] ;
struct netconfmsg * ncm ;
struct net_device * dev ;
struct mpls_dev * mdev ;
struct sk_buff * skb ;
int ifindex ;
int err ;
err = nlmsg_parse ( nlh , sizeof ( * ncm ) , tb , NETCONFA_MAX ,
devconf_mpls_policy ) ;
if ( err < 0 )
goto errout ;
err = - EINVAL ;
if ( ! tb [ NETCONFA_IFINDEX ] )
goto errout ;
ifindex = nla_get_s32 ( tb [ NETCONFA_IFINDEX ] ) ;
dev = __dev_get_by_index ( net , ifindex ) ;
if ( ! dev )
goto errout ;
mdev = mpls_dev_get ( dev ) ;
if ( ! mdev )
goto errout ;
err = - ENOBUFS ;
skb = nlmsg_new ( mpls_netconf_msgsize_devconf ( NETCONFA_ALL ) , GFP_KERNEL ) ;
if ( ! skb )
goto errout ;
err = mpls_netconf_fill_devconf ( skb , mdev ,
NETLINK_CB ( in_skb ) . portid ,
nlh - > nlmsg_seq , RTM_NEWNETCONF , 0 ,
NETCONFA_ALL ) ;
if ( err < 0 ) {
/* -EMSGSIZE implies BUG in mpls_netconf_msgsize_devconf() */
WARN_ON ( err = = - EMSGSIZE ) ;
kfree_skb ( skb ) ;
goto errout ;
}
err = rtnl_unicast ( skb , net , NETLINK_CB ( in_skb ) . portid ) ;
errout :
return err ;
}
static int mpls_netconf_dump_devconf ( struct sk_buff * skb ,
struct netlink_callback * cb )
{
struct net * net = sock_net ( skb - > sk ) ;
struct hlist_head * head ;
struct net_device * dev ;
struct mpls_dev * mdev ;
int idx , s_idx ;
int h , s_h ;
s_h = cb - > args [ 0 ] ;
s_idx = idx = cb - > args [ 1 ] ;
for ( h = s_h ; h < NETDEV_HASHENTRIES ; h + + , s_idx = 0 ) {
idx = 0 ;
head = & net - > dev_index_head [ h ] ;
rcu_read_lock ( ) ;
cb - > seq = net - > dev_base_seq ;
hlist_for_each_entry_rcu ( dev , head , index_hlist ) {
if ( idx < s_idx )
goto cont ;
mdev = mpls_dev_get ( dev ) ;
if ( ! mdev )
goto cont ;
if ( mpls_netconf_fill_devconf ( skb , mdev ,
NETLINK_CB ( cb - > skb ) . portid ,
cb - > nlh - > nlmsg_seq ,
RTM_NEWNETCONF ,
NLM_F_MULTI ,
NETCONFA_ALL ) < 0 ) {
rcu_read_unlock ( ) ;
goto done ;
}
nl_dump_check_consistent ( cb , nlmsg_hdr ( skb ) ) ;
cont :
idx + + ;
}
rcu_read_unlock ( ) ;
}
done :
cb - > args [ 0 ] = h ;
cb - > args [ 1 ] = idx ;
return skb - > len ;
}
2015-04-22 11:14:38 +01:00
# define MPLS_PERDEV_SYSCTL_OFFSET(field) \
( & ( ( struct mpls_dev * ) 0 ) - > field )
2017-02-20 08:03:30 -08:00
static int mpls_conf_proc ( struct ctl_table * ctl , int write ,
void __user * buffer ,
size_t * lenp , loff_t * ppos )
{
int oval = * ( int * ) ctl - > data ;
int ret = proc_dointvec ( ctl , write , buffer , lenp , ppos ) ;
if ( write ) {
struct mpls_dev * mdev = ctl - > extra1 ;
int i = ( int * ) ctl - > data - ( int * ) mdev ;
struct net * net = ctl - > extra2 ;
int val = * ( int * ) ctl - > data ;
if ( i = = offsetof ( struct mpls_dev , input_enabled ) & &
val ! = oval ) {
mpls_netconf_notify_devconf ( net ,
NETCONFA_INPUT ,
mdev ) ;
}
}
return ret ;
}
2015-04-22 11:14:38 +01:00
static const struct ctl_table mpls_dev_table [ ] = {
{
. procname = " input " ,
. maxlen = sizeof ( int ) ,
. mode = 0644 ,
2017-02-20 08:03:30 -08:00
. proc_handler = mpls_conf_proc ,
2015-04-22 11:14:38 +01:00
. data = MPLS_PERDEV_SYSCTL_OFFSET ( input_enabled ) ,
} ,
{ }
} ;
static int mpls_dev_sysctl_register ( struct net_device * dev ,
struct mpls_dev * mdev )
{
char path [ sizeof ( " net/mpls/conf/ " ) + IFNAMSIZ ] ;
2017-02-20 08:03:30 -08:00
struct net * net = dev_net ( dev ) ;
2015-04-22 11:14:38 +01:00
struct ctl_table * table ;
int i ;
table = kmemdup ( & mpls_dev_table , sizeof ( mpls_dev_table ) , GFP_KERNEL ) ;
if ( ! table )
goto out ;
/* Table data contains only offsets relative to the base of
* the mdev at this point , so make them absolute .
*/
2017-02-20 08:03:30 -08:00
for ( i = 0 ; i < ARRAY_SIZE ( mpls_dev_table ) ; i + + ) {
2015-04-22 11:14:38 +01:00
table [ i ] . data = ( char * ) mdev + ( uintptr_t ) table [ i ] . data ;
2017-02-20 08:03:30 -08:00
table [ i ] . extra1 = mdev ;
table [ i ] . extra2 = net ;
}
2015-04-22 11:14:38 +01:00
snprintf ( path , sizeof ( path ) , " net/mpls/conf/%s " , dev - > name ) ;
mdev - > sysctl = register_net_sysctl ( dev_net ( dev ) , path , table ) ;
if ( ! mdev - > sysctl )
goto free ;
return 0 ;
free :
kfree ( table ) ;
out :
return - ENOBUFS ;
}
static void mpls_dev_sysctl_unregister ( struct mpls_dev * mdev )
{
struct ctl_table * table ;
table = mdev - > sysctl - > ctl_table_arg ;
unregister_net_sysctl_table ( mdev - > sysctl ) ;
kfree ( table ) ;
}
2015-04-22 11:14:37 +01:00
static struct mpls_dev * mpls_add_dev ( struct net_device * dev )
{
struct mpls_dev * mdev ;
int err = - ENOMEM ;
2017-01-16 14:16:37 +00:00
int i ;
2015-04-22 11:14:37 +01:00
ASSERT_RTNL ( ) ;
mdev = kzalloc ( sizeof ( * mdev ) , GFP_KERNEL ) ;
if ( ! mdev )
return ERR_PTR ( err ) ;
2017-01-16 14:16:37 +00:00
mdev - > stats = alloc_percpu ( struct mpls_pcpu_stats ) ;
if ( ! mdev - > stats )
goto free ;
for_each_possible_cpu ( i ) {
struct mpls_pcpu_stats * mpls_stats ;
mpls_stats = per_cpu_ptr ( mdev - > stats , i ) ;
u64_stats_init ( & mpls_stats - > syncp ) ;
}
2015-04-22 11:14:38 +01:00
err = mpls_dev_sysctl_register ( dev , mdev ) ;
if ( err )
goto free ;
2017-02-20 08:03:30 -08:00
mdev - > dev = dev ;
2015-04-22 11:14:37 +01:00
rcu_assign_pointer ( dev - > mpls_ptr , mdev ) ;
return mdev ;
2015-04-22 11:14:38 +01:00
free :
2017-01-16 14:16:37 +00:00
free_percpu ( mdev - > stats ) ;
2015-04-22 11:14:38 +01:00
kfree ( mdev ) ;
return ERR_PTR ( err ) ;
2015-04-22 11:14:37 +01:00
}
2017-01-16 14:16:37 +00:00
static void mpls_dev_destroy_rcu ( struct rcu_head * head )
{
struct mpls_dev * mdev = container_of ( head , struct mpls_dev , rcu ) ;
free_percpu ( mdev - > stats ) ;
kfree ( mdev ) ;
}
2015-12-01 22:18:11 -08:00
static void mpls_ifdown ( struct net_device * dev , int event )
2015-03-03 19:10:47 -06:00
{
2015-03-07 16:21:56 -06:00
struct mpls_route __rcu * * platform_label ;
2015-03-03 19:10:47 -06:00
struct net * net = dev_net ( dev ) ;
unsigned index ;
2015-03-07 16:21:56 -06:00
platform_label = rtnl_dereference ( net - > mpls . platform_label ) ;
2015-03-03 19:10:47 -06:00
for ( index = 0 ; index < net - > mpls . platform_labels ; index + + ) {
2015-03-07 16:21:56 -06:00
struct mpls_route * rt = rtnl_dereference ( platform_label [ index ] ) ;
2015-12-01 22:18:11 -08:00
2015-03-03 19:10:47 -06:00
if ( ! rt )
continue ;
2015-12-01 22:18:11 -08:00
change_nexthops ( rt ) {
2015-10-23 06:03:27 -07:00
if ( rtnl_dereference ( nh - > nh_dev ) ! = dev )
continue ;
2015-12-01 22:18:11 -08:00
switch ( event ) {
case NETDEV_DOWN :
case NETDEV_UNREGISTER :
nh - > nh_flags | = RTNH_F_DEAD ;
/* fall through */
case NETDEV_CHANGE :
nh - > nh_flags | = RTNH_F_LINKDOWN ;
ACCESS_ONCE ( rt - > rt_nhn_alive ) = rt - > rt_nhn_alive - 1 ;
break ;
}
if ( event = = NETDEV_UNREGISTER )
RCU_INIT_POINTER ( nh - > nh_dev , NULL ) ;
2015-10-23 06:03:27 -07:00
} endfor_nexthops ( rt ) ;
2015-03-03 19:10:47 -06:00
}
2015-12-01 22:18:11 -08:00
}
static void mpls_ifup ( struct net_device * dev , unsigned int nh_flags )
{
struct mpls_route __rcu * * platform_label ;
struct net * net = dev_net ( dev ) ;
unsigned index ;
int alive ;
platform_label = rtnl_dereference ( net - > mpls . platform_label ) ;
for ( index = 0 ; index < net - > mpls . platform_labels ; index + + ) {
struct mpls_route * rt = rtnl_dereference ( platform_label [ index ] ) ;
if ( ! rt )
continue ;
2015-04-22 11:14:38 +01:00
2015-12-01 22:18:11 -08:00
alive = 0 ;
change_nexthops ( rt ) {
struct net_device * nh_dev =
rtnl_dereference ( nh - > nh_dev ) ;
2015-04-22 11:14:37 +01:00
2015-12-01 22:18:11 -08:00
if ( ! ( nh - > nh_flags & nh_flags ) ) {
alive + + ;
continue ;
}
if ( nh_dev ! = dev )
continue ;
alive + + ;
nh - > nh_flags & = ~ nh_flags ;
} endfor_nexthops ( rt ) ;
ACCESS_ONCE ( rt - > rt_nhn_alive ) = alive ;
}
2015-03-03 19:10:47 -06:00
}
static int mpls_dev_notify ( struct notifier_block * this , unsigned long event ,
void * ptr )
{
struct net_device * dev = netdev_notifier_info_to_dev ( ptr ) ;
2015-04-22 11:14:37 +01:00
struct mpls_dev * mdev ;
2015-12-01 22:18:11 -08:00
unsigned int flags ;
2015-03-03 19:10:47 -06:00
2015-12-01 22:18:11 -08:00
if ( event = = NETDEV_REGISTER ) {
2016-07-07 07:56:15 +02:00
/* For now just support Ethernet, IPGRE, SIT and IPIP devices */
2016-06-16 17:09:09 +09:00
if ( dev - > type = = ARPHRD_ETHER | |
dev - > type = = ARPHRD_LOOPBACK | |
2016-07-07 07:56:15 +02:00
dev - > type = = ARPHRD_IPGRE | |
dev - > type = = ARPHRD_SIT | |
dev - > type = = ARPHRD_TUNNEL ) {
2015-04-22 11:14:37 +01:00
mdev = mpls_add_dev ( dev ) ;
if ( IS_ERR ( mdev ) )
return notifier_from_errno ( PTR_ERR ( mdev ) ) ;
}
2015-12-01 22:18:11 -08:00
return NOTIFY_OK ;
}
2015-04-22 11:14:37 +01:00
2015-12-01 22:18:11 -08:00
mdev = mpls_dev_get ( dev ) ;
if ( ! mdev )
return NOTIFY_OK ;
switch ( event ) {
case NETDEV_DOWN :
mpls_ifdown ( dev , event ) ;
break ;
case NETDEV_UP :
flags = dev_get_flags ( dev ) ;
if ( flags & ( IFF_RUNNING | IFF_LOWER_UP ) )
mpls_ifup ( dev , RTNH_F_DEAD | RTNH_F_LINKDOWN ) ;
else
mpls_ifup ( dev , RTNH_F_DEAD ) ;
break ;
case NETDEV_CHANGE :
flags = dev_get_flags ( dev ) ;
if ( flags & ( IFF_RUNNING | IFF_LOWER_UP ) )
mpls_ifup ( dev , RTNH_F_DEAD | RTNH_F_LINKDOWN ) ;
else
mpls_ifdown ( dev , event ) ;
break ;
2015-03-03 19:10:47 -06:00
case NETDEV_UNREGISTER :
2015-12-01 22:18:11 -08:00
mpls_ifdown ( dev , event ) ;
mdev = mpls_dev_get ( dev ) ;
if ( mdev ) {
mpls_dev_sysctl_unregister ( mdev ) ;
RCU_INIT_POINTER ( dev - > mpls_ptr , NULL ) ;
2017-01-16 14:16:37 +00:00
call_rcu ( & mdev - > rcu , mpls_dev_destroy_rcu ) ;
2015-12-01 22:18:11 -08:00
}
2015-03-03 19:10:47 -06:00
break ;
2015-06-11 19:58:26 +01:00
case NETDEV_CHANGENAME :
mdev = mpls_dev_get ( dev ) ;
if ( mdev ) {
int err ;
mpls_dev_sysctl_unregister ( mdev ) ;
err = mpls_dev_sysctl_register ( dev , mdev ) ;
if ( err )
return notifier_from_errno ( err ) ;
}
break ;
2015-03-03 19:10:47 -06:00
}
return NOTIFY_OK ;
}
static struct notifier_block mpls_dev_notifier = {
. notifier_call = mpls_dev_notify ,
} ;
2015-03-03 19:13:56 -06:00
static int nla_put_via ( struct sk_buff * skb ,
2015-03-07 16:25:56 -06:00
u8 table , const void * addr , int alen )
2015-03-03 19:13:56 -06:00
{
2015-03-07 16:25:56 -06:00
static const int table_to_family [ NEIGH_NR_TABLES + 1 ] = {
AF_INET , AF_INET6 , AF_DECnet , AF_PACKET ,
} ;
2015-03-03 19:13:56 -06:00
struct nlattr * nla ;
struct rtvia * via ;
2015-03-07 16:25:56 -06:00
int family = AF_UNSPEC ;
2015-03-03 19:13:56 -06:00
nla = nla_reserve ( skb , RTA_VIA , alen + 2 ) ;
if ( ! nla )
return - EMSGSIZE ;
2015-03-07 16:25:56 -06:00
if ( table < = NEIGH_NR_TABLES )
family = table_to_family [ table ] ;
2015-03-03 19:13:56 -06:00
via = nla_data ( nla ) ;
via - > rtvia_family = family ;
memcpy ( via - > rtvia_addr , addr , alen ) ;
return 0 ;
}
2015-03-03 19:13:19 -06:00
int nla_put_labels ( struct sk_buff * skb , int attrtype ,
u8 labels , const u32 label [ ] )
{
struct nlattr * nla ;
struct mpls_shim_hdr * nla_label ;
bool bos ;
int i ;
nla = nla_reserve ( skb , attrtype , labels * 4 ) ;
if ( ! nla )
return - EMSGSIZE ;
nla_label = nla_data ( nla ) ;
bos = true ;
for ( i = labels - 1 ; i > = 0 ; i - - ) {
nla_label [ i ] = mpls_entry_encode ( label [ i ] , 0 , 0 , bos ) ;
bos = false ;
}
return 0 ;
}
2015-07-21 10:43:52 +02:00
EXPORT_SYMBOL_GPL ( nla_put_labels ) ;
2015-03-03 19:13:19 -06:00
int nla_get_labels ( const struct nlattr * nla ,
2015-10-23 06:03:27 -07:00
u32 max_labels , u8 * labels , u32 label [ ] )
2015-03-03 19:13:19 -06:00
{
unsigned len = nla_len ( nla ) ;
unsigned nla_labels ;
struct mpls_shim_hdr * nla_label ;
bool bos ;
int i ;
/* len needs to be an even multiple of 4 (the label size) */
if ( len & 3 )
return - EINVAL ;
/* Limit the number of new labels allowed */
nla_labels = len / 4 ;
if ( nla_labels > max_labels )
return - EINVAL ;
nla_label = nla_data ( nla ) ;
bos = true ;
for ( i = nla_labels - 1 ; i > = 0 ; i - - , bos = false ) {
struct mpls_entry_decoded dec ;
dec = mpls_entry_decode ( nla_label + i ) ;
/* Ensure the bottom of stack flag is properly set
* and ttl and tc are both clear .
*/
if ( ( dec . bos ! = bos ) | | dec . ttl | | dec . tc )
return - EINVAL ;
2015-04-22 11:14:39 +01:00
switch ( dec . label ) {
2015-05-07 08:08:51 -07:00
case MPLS_LABEL_IMPLNULL :
2015-04-22 11:14:39 +01:00
/* RFC3032: This is a label that an LSR may
* assign and distribute , but which never
* actually appears in the encapsulation .
*/
return - EINVAL ;
}
2015-03-03 19:13:19 -06:00
label [ i ] = dec . label ;
}
* labels = nla_labels ;
return 0 ;
}
2015-07-21 10:43:52 +02:00
EXPORT_SYMBOL_GPL ( nla_get_labels ) ;
2015-03-03 19:13:19 -06:00
2015-10-23 06:03:27 -07:00
int nla_get_via ( const struct nlattr * nla , u8 * via_alen ,
u8 * via_table , u8 via_addr [ ] )
{
struct rtvia * via = nla_data ( nla ) ;
int err = - EINVAL ;
int alen ;
if ( nla_len ( nla ) < offsetof ( struct rtvia , rtvia_addr ) )
goto errout ;
alen = nla_len ( nla ) -
offsetof ( struct rtvia , rtvia_addr ) ;
if ( alen > MAX_VIA_ALEN )
goto errout ;
/* Validate the address family */
switch ( via - > rtvia_family ) {
case AF_PACKET :
* via_table = NEIGH_LINK_TABLE ;
break ;
case AF_INET :
* via_table = NEIGH_ARP_TABLE ;
if ( alen ! = 4 )
goto errout ;
break ;
case AF_INET6 :
* via_table = NEIGH_ND_TABLE ;
if ( alen ! = 16 )
goto errout ;
break ;
default :
/* Unsupported address family */
goto errout ;
}
memcpy ( via_addr , via - > rtvia_addr , alen ) ;
* via_alen = alen ;
err = 0 ;
errout :
return err ;
}
2015-03-03 19:13:56 -06:00
static int rtm_to_route_config ( struct sk_buff * skb , struct nlmsghdr * nlh ,
struct mpls_route_config * cfg )
{
struct rtmsg * rtm ;
struct nlattr * tb [ RTA_MAX + 1 ] ;
int index ;
int err ;
err = nlmsg_parse ( nlh , sizeof ( * rtm ) , tb , RTA_MAX , rtm_mpls_policy ) ;
if ( err < 0 )
goto errout ;
err = - EINVAL ;
rtm = nlmsg_data ( nlh ) ;
memset ( cfg , 0 , sizeof ( * cfg ) ) ;
if ( rtm - > rtm_family ! = AF_MPLS )
goto errout ;
if ( rtm - > rtm_dst_len ! = 20 )
goto errout ;
if ( rtm - > rtm_src_len ! = 0 )
goto errout ;
if ( rtm - > rtm_tos ! = 0 )
goto errout ;
if ( rtm - > rtm_table ! = RT_TABLE_MAIN )
goto errout ;
/* Any value is acceptable for rtm_protocol */
/* As mpls uses destination specific addresses
* ( or source specific address in the case of multicast )
* all addresses have universal scope .
*/
if ( rtm - > rtm_scope ! = RT_SCOPE_UNIVERSE )
goto errout ;
if ( rtm - > rtm_type ! = RTN_UNICAST )
goto errout ;
if ( rtm - > rtm_flags ! = 0 )
goto errout ;
cfg - > rc_label = LABEL_NOT_SPECIFIED ;
cfg - > rc_protocol = rtm - > rtm_protocol ;
2015-12-10 19:30:50 +00:00
cfg - > rc_via_table = MPLS_NEIGH_TABLE_UNSPEC ;
2017-03-10 20:43:24 +00:00
cfg - > rc_ttl_propagate = MPLS_TTL_PROP_DEFAULT ;
2015-03-03 19:13:56 -06:00
cfg - > rc_nlflags = nlh - > nlmsg_flags ;
cfg - > rc_nlinfo . portid = NETLINK_CB ( skb ) . portid ;
cfg - > rc_nlinfo . nlh = nlh ;
cfg - > rc_nlinfo . nl_net = sock_net ( skb - > sk ) ;
for ( index = 0 ; index < = RTA_MAX ; index + + ) {
struct nlattr * nla = tb [ index ] ;
if ( ! nla )
continue ;
2016-12-03 07:59:26 +00:00
switch ( index ) {
2015-03-03 19:13:56 -06:00
case RTA_OIF :
cfg - > rc_ifindex = nla_get_u32 ( nla ) ;
break ;
case RTA_NEWDST :
if ( nla_get_labels ( nla , MAX_NEW_LABELS ,
& cfg - > rc_output_labels ,
cfg - > rc_output_label ) )
goto errout ;
break ;
case RTA_DST :
{
2015-10-23 06:03:27 -07:00
u8 label_count ;
2015-03-03 19:13:56 -06:00
if ( nla_get_labels ( nla , 1 , & label_count ,
& cfg - > rc_label ) )
goto errout ;
2015-08-03 17:50:04 +01:00
/* Reserved labels may not be set */
if ( cfg - > rc_label < MPLS_LABEL_FIRST_UNRESERVED )
2015-03-03 19:13:56 -06:00
goto errout ;
break ;
}
case RTA_VIA :
{
2015-10-23 06:03:27 -07:00
if ( nla_get_via ( nla , & cfg - > rc_via_alen ,
& cfg - > rc_via_table , cfg - > rc_via ) )
2015-03-03 19:13:56 -06:00
goto errout ;
2015-10-23 06:03:27 -07:00
break ;
}
case RTA_MULTIPATH :
{
cfg - > rc_mp = nla_data ( nla ) ;
cfg - > rc_mp_len = nla_len ( nla ) ;
2015-03-03 19:13:56 -06:00
break ;
}
2017-03-10 20:43:24 +00:00
case RTA_TTL_PROPAGATE :
{
u8 ttl_propagate = nla_get_u8 ( nla ) ;
if ( ttl_propagate > 1 )
goto errout ;
cfg - > rc_ttl_propagate = ttl_propagate ?
MPLS_TTL_PROP_ENABLED :
MPLS_TTL_PROP_DISABLED ;
break ;
}
2015-03-03 19:13:56 -06:00
default :
/* Unsupported attribute */
goto errout ;
}
}
err = 0 ;
errout :
return err ;
}
static int mpls_rtm_delroute ( struct sk_buff * skb , struct nlmsghdr * nlh )
{
struct mpls_route_config cfg ;
int err ;
err = rtm_to_route_config ( skb , nlh , & cfg ) ;
if ( err < 0 )
return err ;
return mpls_route_del ( & cfg ) ;
}
static int mpls_rtm_newroute ( struct sk_buff * skb , struct nlmsghdr * nlh )
{
struct mpls_route_config cfg ;
int err ;
err = rtm_to_route_config ( skb , nlh , & cfg ) ;
if ( err < 0 )
return err ;
return mpls_route_add ( & cfg ) ;
}
static int mpls_dump_route ( struct sk_buff * skb , u32 portid , u32 seq , int event ,
u32 label , struct mpls_route * rt , int flags )
{
2015-03-07 16:21:56 -06:00
struct net_device * dev ;
2015-03-03 19:13:56 -06:00
struct nlmsghdr * nlh ;
struct rtmsg * rtm ;
nlh = nlmsg_put ( skb , portid , seq , event , sizeof ( * rtm ) , flags ) ;
if ( nlh = = NULL )
return - EMSGSIZE ;
rtm = nlmsg_data ( nlh ) ;
rtm - > rtm_family = AF_MPLS ;
rtm - > rtm_dst_len = 20 ;
rtm - > rtm_src_len = 0 ;
rtm - > rtm_tos = 0 ;
rtm - > rtm_table = RT_TABLE_MAIN ;
rtm - > rtm_protocol = rt - > rt_protocol ;
rtm - > rtm_scope = RT_SCOPE_UNIVERSE ;
rtm - > rtm_type = RTN_UNICAST ;
rtm - > rtm_flags = 0 ;
if ( nla_put_labels ( skb , RTA_DST , 1 , & label ) )
goto nla_put_failure ;
2017-03-10 20:43:24 +00:00
if ( rt - > rt_ttl_propagate ! = MPLS_TTL_PROP_DEFAULT ) {
bool ttl_propagate =
rt - > rt_ttl_propagate = = MPLS_TTL_PROP_ENABLED ;
if ( nla_put_u8 ( skb , RTA_TTL_PROPAGATE ,
ttl_propagate ) )
goto nla_put_failure ;
}
2015-10-23 06:03:27 -07:00
if ( rt - > rt_nhn = = 1 ) {
2015-10-27 00:37:36 +00:00
const struct mpls_nh * nh = rt - > rt_nh ;
2015-10-23 06:03:27 -07:00
if ( nh - > nh_labels & &
nla_put_labels ( skb , RTA_NEWDST , nh - > nh_labels ,
nh - > nh_label ) )
goto nla_put_failure ;
2015-12-10 19:30:50 +00:00
if ( nh - > nh_via_table ! = MPLS_NEIGH_TABLE_UNSPEC & &
2015-12-10 19:30:49 +00:00
nla_put_via ( skb , nh - > nh_via_table , mpls_nh_via ( rt , nh ) ,
2015-10-23 06:03:27 -07:00
nh - > nh_via_alen ) )
goto nla_put_failure ;
dev = rtnl_dereference ( nh - > nh_dev ) ;
if ( dev & & nla_put_u32 ( skb , RTA_OIF , dev - > ifindex ) )
goto nla_put_failure ;
2015-12-01 22:18:11 -08:00
if ( nh - > nh_flags & RTNH_F_LINKDOWN )
rtm - > rtm_flags | = RTNH_F_LINKDOWN ;
if ( nh - > nh_flags & RTNH_F_DEAD )
rtm - > rtm_flags | = RTNH_F_DEAD ;
2015-10-23 06:03:27 -07:00
} else {
struct rtnexthop * rtnh ;
struct nlattr * mp ;
2015-12-01 22:18:11 -08:00
int dead = 0 ;
int linkdown = 0 ;
2015-10-23 06:03:27 -07:00
mp = nla_nest_start ( skb , RTA_MULTIPATH ) ;
if ( ! mp )
goto nla_put_failure ;
for_nexthops ( rt ) {
rtnh = nla_reserve_nohdr ( skb , sizeof ( * rtnh ) ) ;
if ( ! rtnh )
goto nla_put_failure ;
dev = rtnl_dereference ( nh - > nh_dev ) ;
if ( dev )
rtnh - > rtnh_ifindex = dev - > ifindex ;
2015-12-01 22:18:11 -08:00
if ( nh - > nh_flags & RTNH_F_LINKDOWN ) {
rtnh - > rtnh_flags | = RTNH_F_LINKDOWN ;
linkdown + + ;
}
if ( nh - > nh_flags & RTNH_F_DEAD ) {
rtnh - > rtnh_flags | = RTNH_F_DEAD ;
dead + + ;
}
2015-10-23 06:03:27 -07:00
if ( nh - > nh_labels & & nla_put_labels ( skb , RTA_NEWDST ,
nh - > nh_labels ,
nh - > nh_label ) )
goto nla_put_failure ;
2015-12-10 19:30:51 +00:00
if ( nh - > nh_via_table ! = MPLS_NEIGH_TABLE_UNSPEC & &
nla_put_via ( skb , nh - > nh_via_table ,
2015-10-27 00:37:36 +00:00
mpls_nh_via ( rt , nh ) ,
2015-10-23 06:03:27 -07:00
nh - > nh_via_alen ) )
goto nla_put_failure ;
/* length of rtnetlink header + attributes */
rtnh - > rtnh_len = nlmsg_get_pos ( skb ) - ( void * ) rtnh ;
} endfor_nexthops ( rt ) ;
2015-12-01 22:18:11 -08:00
if ( linkdown = = rt - > rt_nhn )
rtm - > rtm_flags | = RTNH_F_LINKDOWN ;
if ( dead = = rt - > rt_nhn )
rtm - > rtm_flags | = RTNH_F_DEAD ;
2015-10-23 06:03:27 -07:00
nla_nest_end ( skb , mp ) ;
}
2015-03-03 19:13:56 -06:00
nlmsg_end ( skb , nlh ) ;
return 0 ;
nla_put_failure :
nlmsg_cancel ( skb , nlh ) ;
return - EMSGSIZE ;
}
static int mpls_dump_routes ( struct sk_buff * skb , struct netlink_callback * cb )
{
struct net * net = sock_net ( skb - > sk ) ;
2015-03-07 16:21:56 -06:00
struct mpls_route __rcu * * platform_label ;
size_t platform_labels ;
2015-03-03 19:13:56 -06:00
unsigned int index ;
ASSERT_RTNL ( ) ;
index = cb - > args [ 0 ] ;
2015-08-03 17:50:04 +01:00
if ( index < MPLS_LABEL_FIRST_UNRESERVED )
index = MPLS_LABEL_FIRST_UNRESERVED ;
2015-03-03 19:13:56 -06:00
2015-03-07 16:21:56 -06:00
platform_label = rtnl_dereference ( net - > mpls . platform_label ) ;
platform_labels = net - > mpls . platform_labels ;
for ( ; index < platform_labels ; index + + ) {
2015-03-03 19:13:56 -06:00
struct mpls_route * rt ;
2015-03-07 16:21:56 -06:00
rt = rtnl_dereference ( platform_label [ index ] ) ;
2015-03-03 19:13:56 -06:00
if ( ! rt )
continue ;
if ( mpls_dump_route ( skb , NETLINK_CB ( cb - > skb ) . portid ,
cb - > nlh - > nlmsg_seq , RTM_NEWROUTE ,
index , rt , NLM_F_MULTI ) < 0 )
break ;
}
cb - > args [ 0 ] = index ;
return skb - > len ;
}
2015-03-03 19:14:31 -06:00
static inline size_t lfib_nlmsg_size ( struct mpls_route * rt )
{
size_t payload =
NLMSG_ALIGN ( sizeof ( struct rtmsg ) )
2017-03-10 20:43:24 +00:00
+ nla_total_size ( 4 ) /* RTA_DST */
+ nla_total_size ( 1 ) ; /* RTA_TTL_PROPAGATE */
2015-10-23 06:03:27 -07:00
if ( rt - > rt_nhn = = 1 ) {
struct mpls_nh * nh = rt - > rt_nh ;
if ( nh - > nh_dev )
payload + = nla_total_size ( 4 ) ; /* RTA_OIF */
2015-12-10 19:30:50 +00:00
if ( nh - > nh_via_table ! = MPLS_NEIGH_TABLE_UNSPEC ) /* RTA_VIA */
2015-12-10 19:30:49 +00:00
payload + = nla_total_size ( 2 + nh - > nh_via_alen ) ;
2015-10-23 06:03:27 -07:00
if ( nh - > nh_labels ) /* RTA_NEWDST */
payload + = nla_total_size ( nh - > nh_labels * 4 ) ;
} else {
/* each nexthop is packed in an attribute */
size_t nhsize = 0 ;
for_nexthops ( rt ) {
nhsize + = nla_total_size ( sizeof ( struct rtnexthop ) ) ;
2015-12-10 19:30:51 +00:00
/* RTA_VIA */
if ( nh - > nh_via_table ! = MPLS_NEIGH_TABLE_UNSPEC )
nhsize + = nla_total_size ( 2 + nh - > nh_via_alen ) ;
2015-10-23 06:03:27 -07:00
if ( nh - > nh_labels )
nhsize + = nla_total_size ( nh - > nh_labels * 4 ) ;
} endfor_nexthops ( rt ) ;
/* nested attribute */
payload + = nla_total_size ( nhsize ) ;
}
2015-03-03 19:14:31 -06:00
return payload ;
}
static void rtmsg_lfib ( int event , u32 label , struct mpls_route * rt ,
struct nlmsghdr * nlh , struct net * net , u32 portid ,
unsigned int nlm_flags )
{
struct sk_buff * skb ;
u32 seq = nlh ? nlh - > nlmsg_seq : 0 ;
int err = - ENOBUFS ;
skb = nlmsg_new ( lfib_nlmsg_size ( rt ) , GFP_KERNEL ) ;
if ( skb = = NULL )
goto errout ;
err = mpls_dump_route ( skb , portid , seq , event , label , rt , nlm_flags ) ;
if ( err < 0 ) {
/* -EMSGSIZE implies BUG in lfib_nlmsg_size */
WARN_ON ( err = = - EMSGSIZE ) ;
kfree_skb ( skb ) ;
goto errout ;
}
rtnl_notify ( skb , net , portid , RTNLGRP_MPLS_ROUTE , nlh , GFP_KERNEL ) ;
return ;
errout :
if ( err < 0 )
rtnl_set_sk_err ( net , RTNLGRP_MPLS_ROUTE , err ) ;
}
2015-03-03 19:11:20 -06:00
static int resize_platform_label_table ( struct net * net , size_t limit )
{
size_t size = sizeof ( struct mpls_route * ) * limit ;
size_t old_limit ;
size_t cp_size ;
struct mpls_route __rcu * * labels = NULL , * * old ;
struct mpls_route * rt0 = NULL , * rt2 = NULL ;
unsigned index ;
if ( size ) {
labels = kzalloc ( size , GFP_KERNEL | __GFP_NOWARN | __GFP_NORETRY ) ;
if ( ! labels )
labels = vzalloc ( size ) ;
if ( ! labels )
goto nolabels ;
}
/* In case the predefined labels need to be populated */
2015-05-07 08:08:51 -07:00
if ( limit > MPLS_LABEL_IPV4NULL ) {
2015-03-03 19:11:20 -06:00
struct net_device * lo = net - > loopback_dev ;
2015-10-27 00:37:36 +00:00
rt0 = mpls_rt_alloc ( 1 , lo - > addr_len ) ;
2015-03-03 19:11:20 -06:00
if ( ! rt0 )
goto nort0 ;
2015-10-23 06:03:27 -07:00
RCU_INIT_POINTER ( rt0 - > rt_nh - > nh_dev , lo ) ;
2015-03-03 19:11:20 -06:00
rt0 - > rt_protocol = RTPROT_KERNEL ;
2015-08-06 11:04:56 +01:00
rt0 - > rt_payload_type = MPT_IPV4 ;
2017-03-10 20:43:24 +00:00
rt0 - > rt_ttl_propagate = MPLS_TTL_PROP_DEFAULT ;
2015-10-23 06:03:27 -07:00
rt0 - > rt_nh - > nh_via_table = NEIGH_LINK_TABLE ;
2015-10-27 00:37:35 +00:00
rt0 - > rt_nh - > nh_via_alen = lo - > addr_len ;
2015-10-27 00:37:36 +00:00
memcpy ( __mpls_nh_via ( rt0 , rt0 - > rt_nh ) , lo - > dev_addr ,
lo - > addr_len ) ;
2015-03-03 19:11:20 -06:00
}
2015-05-07 08:08:51 -07:00
if ( limit > MPLS_LABEL_IPV6NULL ) {
2015-03-03 19:11:20 -06:00
struct net_device * lo = net - > loopback_dev ;
2015-10-27 00:37:36 +00:00
rt2 = mpls_rt_alloc ( 1 , lo - > addr_len ) ;
2015-03-03 19:11:20 -06:00
if ( ! rt2 )
goto nort2 ;
2015-10-23 06:03:27 -07:00
RCU_INIT_POINTER ( rt2 - > rt_nh - > nh_dev , lo ) ;
2015-03-03 19:11:20 -06:00
rt2 - > rt_protocol = RTPROT_KERNEL ;
2015-08-06 11:04:56 +01:00
rt2 - > rt_payload_type = MPT_IPV6 ;
2017-03-10 20:43:24 +00:00
rt0 - > rt_ttl_propagate = MPLS_TTL_PROP_DEFAULT ;
2015-10-23 06:03:27 -07:00
rt2 - > rt_nh - > nh_via_table = NEIGH_LINK_TABLE ;
2015-10-27 00:37:35 +00:00
rt2 - > rt_nh - > nh_via_alen = lo - > addr_len ;
2015-10-27 00:37:36 +00:00
memcpy ( __mpls_nh_via ( rt2 , rt2 - > rt_nh ) , lo - > dev_addr ,
lo - > addr_len ) ;
2015-03-03 19:11:20 -06:00
}
rtnl_lock ( ) ;
/* Remember the original table */
2015-03-07 16:21:56 -06:00
old = rtnl_dereference ( net - > mpls . platform_label ) ;
2015-03-03 19:11:20 -06:00
old_limit = net - > mpls . platform_labels ;
/* Free any labels beyond the new table */
for ( index = limit ; index < old_limit ; index + + )
2015-10-23 06:03:27 -07:00
mpls_route_update ( net , index , NULL , NULL ) ;
2015-03-03 19:11:20 -06:00
/* Copy over the old labels */
cp_size = size ;
if ( old_limit < limit )
cp_size = old_limit * sizeof ( struct mpls_route * ) ;
memcpy ( labels , old , cp_size ) ;
/* If needed set the predefined labels */
2015-05-07 08:08:51 -07:00
if ( ( old_limit < = MPLS_LABEL_IPV6NULL ) & &
( limit > MPLS_LABEL_IPV6NULL ) ) {
RCU_INIT_POINTER ( labels [ MPLS_LABEL_IPV6NULL ] , rt2 ) ;
2015-03-03 19:11:20 -06:00
rt2 = NULL ;
}
2015-05-07 08:08:51 -07:00
if ( ( old_limit < = MPLS_LABEL_IPV4NULL ) & &
( limit > MPLS_LABEL_IPV4NULL ) ) {
RCU_INIT_POINTER ( labels [ MPLS_LABEL_IPV4NULL ] , rt0 ) ;
2015-03-03 19:11:20 -06:00
rt0 = NULL ;
}
/* Update the global pointers */
net - > mpls . platform_labels = limit ;
2015-03-07 16:21:56 -06:00
rcu_assign_pointer ( net - > mpls . platform_label , labels ) ;
2015-03-03 19:11:20 -06:00
rtnl_unlock ( ) ;
mpls_rt_free ( rt2 ) ;
mpls_rt_free ( rt0 ) ;
if ( old ) {
synchronize_rcu ( ) ;
kvfree ( old ) ;
}
return 0 ;
nort2 :
mpls_rt_free ( rt0 ) ;
nort0 :
kvfree ( labels ) ;
nolabels :
return - ENOMEM ;
}
static int mpls_platform_labels ( struct ctl_table * table , int write ,
void __user * buffer , size_t * lenp , loff_t * ppos )
{
struct net * net = table - > data ;
int platform_labels = net - > mpls . platform_labels ;
int ret ;
struct ctl_table tmp = {
. procname = table - > procname ,
. data = & platform_labels ,
. maxlen = sizeof ( int ) ,
. mode = table - > mode ,
. extra1 = & zero ,
. extra2 = & label_limit ,
} ;
ret = proc_dointvec_minmax ( & tmp , write , buffer , lenp , ppos ) ;
if ( write & & ret = = 0 )
ret = resize_platform_label_table ( net , platform_labels ) ;
return ret ;
}
2017-03-10 20:43:24 +00:00
# define MPLS_NS_SYSCTL_OFFSET(field) \
( & ( ( struct net * ) 0 ) - > field )
2015-04-22 11:14:38 +01:00
static const struct ctl_table mpls_table [ ] = {
2015-03-03 19:11:20 -06:00
{
. procname = " platform_labels " ,
. data = NULL ,
. maxlen = sizeof ( int ) ,
. mode = 0644 ,
. proc_handler = mpls_platform_labels ,
} ,
2017-03-10 20:43:24 +00:00
{
. procname = " ip_ttl_propagate " ,
. data = MPLS_NS_SYSCTL_OFFSET ( mpls . ip_ttl_propagate ) ,
. maxlen = sizeof ( int ) ,
. mode = 0644 ,
. proc_handler = proc_dointvec_minmax ,
. extra1 = & zero ,
. extra2 = & one ,
} ,
2015-03-03 19:11:20 -06:00
{ }
} ;
2015-03-03 19:10:47 -06:00
static int mpls_net_init ( struct net * net )
{
2015-03-03 19:11:20 -06:00
struct ctl_table * table ;
2017-03-10 20:43:24 +00:00
int i ;
2015-03-03 19:11:20 -06:00
2015-03-03 19:10:47 -06:00
net - > mpls . platform_labels = 0 ;
net - > mpls . platform_label = NULL ;
2017-03-10 20:43:24 +00:00
net - > mpls . ip_ttl_propagate = 1 ;
2015-03-03 19:10:47 -06:00
2015-03-03 19:11:20 -06:00
table = kmemdup ( mpls_table , sizeof ( mpls_table ) , GFP_KERNEL ) ;
if ( table = = NULL )
return - ENOMEM ;
2017-03-10 20:43:24 +00:00
/* Table data contains only offsets relative to the base of
* the mdev at this point , so make them absolute .
*/
for ( i = 0 ; i < ARRAY_SIZE ( mpls_table ) - 1 ; i + + )
table [ i ] . data = ( char * ) net + ( uintptr_t ) table [ i ] . data ;
2015-03-03 19:11:20 -06:00
net - > mpls . ctl = register_net_sysctl ( net , " net/mpls " , table ) ;
2015-08-31 10:44:19 -07:00
if ( net - > mpls . ctl = = NULL ) {
kfree ( table ) ;
2015-03-03 19:11:20 -06:00
return - ENOMEM ;
2015-08-31 10:44:19 -07:00
}
2015-03-03 19:11:20 -06:00
2015-03-03 19:10:47 -06:00
return 0 ;
}
static void mpls_net_exit ( struct net * net )
{
2015-03-07 16:21:56 -06:00
struct mpls_route __rcu * * platform_label ;
size_t platform_labels ;
2015-03-03 19:11:20 -06:00
struct ctl_table * table ;
2015-03-03 19:10:47 -06:00
unsigned int index ;
2015-03-03 19:11:20 -06:00
table = net - > mpls . ctl - > ctl_table_arg ;
unregister_net_sysctl_table ( net - > mpls . ctl ) ;
kfree ( table ) ;
2015-03-07 16:21:56 -06:00
/* An rcu grace period has passed since there was a device in
* the network namespace ( and thus the last in flight packet )
2015-03-03 19:10:47 -06:00
* left this network namespace . This is because
* unregister_netdevice_many and netdev_run_todo has completed
* for each network device that was in this network namespace .
*
* As such no additional rcu synchronization is necessary when
* freeing the platform_label table .
*/
rtnl_lock ( ) ;
2015-03-07 16:21:56 -06:00
platform_label = rtnl_dereference ( net - > mpls . platform_label ) ;
platform_labels = net - > mpls . platform_labels ;
for ( index = 0 ; index < platform_labels ; index + + ) {
struct mpls_route * rt = rtnl_dereference ( platform_label [ index ] ) ;
RCU_INIT_POINTER ( platform_label [ index ] , NULL ) ;
2015-03-03 19:10:47 -06:00
mpls_rt_free ( rt ) ;
}
rtnl_unlock ( ) ;
2015-03-07 16:21:56 -06:00
kvfree ( platform_label ) ;
2015-03-03 19:10:47 -06:00
}
static struct pernet_operations mpls_net_ops = {
. init = mpls_net_init ,
. exit = mpls_net_exit ,
} ;
2017-01-16 14:16:37 +00:00
static struct rtnl_af_ops mpls_af_ops __read_mostly = {
. family = AF_MPLS ,
. fill_stats_af = mpls_fill_stats_af ,
. get_stats_af_size = mpls_get_stats_af_size ,
} ;
2015-03-03 19:10:47 -06:00
static int __init mpls_init ( void )
{
int err ;
BUILD_BUG_ON ( sizeof ( struct mpls_shim_hdr ) ! = 4 ) ;
err = register_pernet_subsys ( & mpls_net_ops ) ;
if ( err )
goto out ;
err = register_netdevice_notifier ( & mpls_dev_notifier ) ;
if ( err )
goto out_unregister_pernet ;
dev_add_pack ( & mpls_packet_type ) ;
2017-01-16 14:16:37 +00:00
rtnl_af_register ( & mpls_af_ops ) ;
2015-03-03 19:13:56 -06:00
rtnl_register ( PF_MPLS , RTM_NEWROUTE , mpls_rtm_newroute , NULL , NULL ) ;
rtnl_register ( PF_MPLS , RTM_DELROUTE , mpls_rtm_delroute , NULL , NULL ) ;
rtnl_register ( PF_MPLS , RTM_GETROUTE , NULL , mpls_dump_routes , NULL ) ;
2017-02-20 08:03:30 -08:00
rtnl_register ( PF_MPLS , RTM_GETNETCONF , mpls_netconf_get_devconf ,
mpls_netconf_dump_devconf , NULL ) ;
2015-03-03 19:10:47 -06:00
err = 0 ;
out :
return err ;
out_unregister_pernet :
unregister_pernet_subsys ( & mpls_net_ops ) ;
goto out ;
}
module_init ( mpls_init ) ;
static void __exit mpls_exit ( void )
{
2015-03-03 19:13:56 -06:00
rtnl_unregister_all ( PF_MPLS ) ;
2017-01-16 14:16:37 +00:00
rtnl_af_unregister ( & mpls_af_ops ) ;
2015-03-03 19:10:47 -06:00
dev_remove_pack ( & mpls_packet_type ) ;
unregister_netdevice_notifier ( & mpls_dev_notifier ) ;
unregister_pernet_subsys ( & mpls_net_ops ) ;
}
module_exit ( mpls_exit ) ;
MODULE_DESCRIPTION ( " MultiProtocol Label Switching " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS_NETPROTO ( PF_MPLS ) ;