162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci#include <linux/types.h> 362306a36Sopenharmony_ci#include <linux/skbuff.h> 462306a36Sopenharmony_ci#include <linux/socket.h> 562306a36Sopenharmony_ci#include <linux/sysctl.h> 662306a36Sopenharmony_ci#include <linux/net.h> 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/if_arp.h> 962306a36Sopenharmony_ci#include <linux/ipv6.h> 1062306a36Sopenharmony_ci#include <linux/mpls.h> 1162306a36Sopenharmony_ci#include <linux/netconf.h> 1262306a36Sopenharmony_ci#include <linux/nospec.h> 1362306a36Sopenharmony_ci#include <linux/vmalloc.h> 1462306a36Sopenharmony_ci#include <linux/percpu.h> 1562306a36Sopenharmony_ci#include <net/gso.h> 1662306a36Sopenharmony_ci#include <net/ip.h> 1762306a36Sopenharmony_ci#include <net/dst.h> 1862306a36Sopenharmony_ci#include <net/sock.h> 1962306a36Sopenharmony_ci#include <net/arp.h> 2062306a36Sopenharmony_ci#include <net/ip_fib.h> 2162306a36Sopenharmony_ci#include <net/netevent.h> 2262306a36Sopenharmony_ci#include <net/ip_tunnels.h> 2362306a36Sopenharmony_ci#include <net/netns/generic.h> 2462306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6) 2562306a36Sopenharmony_ci#include <net/ipv6.h> 2662306a36Sopenharmony_ci#endif 2762306a36Sopenharmony_ci#include <net/ipv6_stubs.h> 2862306a36Sopenharmony_ci#include <net/rtnh.h> 2962306a36Sopenharmony_ci#include "internal.h" 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/* max memory we will use for mpls_route */ 3262306a36Sopenharmony_ci#define MAX_MPLS_ROUTE_MEM 4096 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/* Maximum number of labels to look ahead at when selecting a path of 3562306a36Sopenharmony_ci * a multipath route 3662306a36Sopenharmony_ci */ 3762306a36Sopenharmony_ci#define MAX_MP_SELECT_LABELS 4 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#define MPLS_NEIGH_TABLE_UNSPEC (NEIGH_LINK_TABLE + 1) 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic int label_limit = (1 << 20) - 1; 4262306a36Sopenharmony_cistatic int ttl_max = 255; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_NET_IP_TUNNEL) 4562306a36Sopenharmony_cistatic size_t ipgre_mpls_encap_hlen(struct ip_tunnel_encap *e) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci return sizeof(struct mpls_shim_hdr); 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic const struct ip_tunnel_encap_ops mpls_iptun_ops = { 5162306a36Sopenharmony_ci .encap_hlen = ipgre_mpls_encap_hlen, 5262306a36Sopenharmony_ci}; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic int ipgre_tunnel_encap_add_mpls_ops(void) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci return ip_tunnel_encap_add_ops(&mpls_iptun_ops, TUNNEL_ENCAP_MPLS); 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic void ipgre_tunnel_encap_del_mpls_ops(void) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci ip_tunnel_encap_del_ops(&mpls_iptun_ops, TUNNEL_ENCAP_MPLS); 6262306a36Sopenharmony_ci} 6362306a36Sopenharmony_ci#else 6462306a36Sopenharmony_cistatic int ipgre_tunnel_encap_add_mpls_ops(void) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci return 0; 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic void ipgre_tunnel_encap_del_mpls_ops(void) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci#endif 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic void rtmsg_lfib(int event, u32 label, struct mpls_route *rt, 7562306a36Sopenharmony_ci struct nlmsghdr *nlh, struct net *net, u32 portid, 7662306a36Sopenharmony_ci unsigned int nlm_flags); 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic struct mpls_route *mpls_route_input_rcu(struct net *net, unsigned index) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci struct mpls_route *rt = NULL; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (index < net->mpls.platform_labels) { 8362306a36Sopenharmony_ci struct mpls_route __rcu **platform_label = 8462306a36Sopenharmony_ci rcu_dereference(net->mpls.platform_label); 8562306a36Sopenharmony_ci rt = rcu_dereference(platform_label[index]); 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci return rt; 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cibool mpls_output_possible(const struct net_device *dev) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci return dev && (dev->flags & IFF_UP) && netif_carrier_ok(dev); 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(mpls_output_possible); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic u8 *__mpls_nh_via(struct mpls_route *rt, struct mpls_nh *nh) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci return (u8 *)nh + rt->rt_via_offset; 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cistatic const u8 *mpls_nh_via(const struct mpls_route *rt, 10262306a36Sopenharmony_ci const struct mpls_nh *nh) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci return __mpls_nh_via((struct mpls_route *)rt, (struct mpls_nh *)nh); 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistatic unsigned int mpls_nh_header_size(const struct mpls_nh *nh) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci /* The size of the layer 2.5 labels to be added for this route */ 11062306a36Sopenharmony_ci return nh->nh_labels * sizeof(struct mpls_shim_hdr); 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ciunsigned int mpls_dev_mtu(const struct net_device *dev) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci /* The amount of data the layer 2 frame can hold */ 11662306a36Sopenharmony_ci return dev->mtu; 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(mpls_dev_mtu); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cibool mpls_pkt_too_big(const struct sk_buff *skb, unsigned int mtu) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci if (skb->len <= mtu) 12362306a36Sopenharmony_ci return false; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (skb_is_gso(skb) && skb_gso_validate_network_len(skb, mtu)) 12662306a36Sopenharmony_ci return false; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci return true; 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(mpls_pkt_too_big); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_civoid mpls_stats_inc_outucastpkts(struct net_device *dev, 13362306a36Sopenharmony_ci const struct sk_buff *skb) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci struct mpls_dev *mdev; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if (skb->protocol == htons(ETH_P_MPLS_UC)) { 13862306a36Sopenharmony_ci mdev = mpls_dev_get(dev); 13962306a36Sopenharmony_ci if (mdev) 14062306a36Sopenharmony_ci MPLS_INC_STATS_LEN(mdev, skb->len, 14162306a36Sopenharmony_ci tx_packets, 14262306a36Sopenharmony_ci tx_bytes); 14362306a36Sopenharmony_ci } else if (skb->protocol == htons(ETH_P_IP)) { 14462306a36Sopenharmony_ci IP_UPD_PO_STATS(dev_net(dev), IPSTATS_MIB_OUT, skb->len); 14562306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6) 14662306a36Sopenharmony_ci } else if (skb->protocol == htons(ETH_P_IPV6)) { 14762306a36Sopenharmony_ci struct inet6_dev *in6dev = __in6_dev_get(dev); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci if (in6dev) 15062306a36Sopenharmony_ci IP6_UPD_PO_STATS(dev_net(dev), in6dev, 15162306a36Sopenharmony_ci IPSTATS_MIB_OUT, skb->len); 15262306a36Sopenharmony_ci#endif 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(mpls_stats_inc_outucastpkts); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_cistatic u32 mpls_multipath_hash(struct mpls_route *rt, struct sk_buff *skb) 15862306a36Sopenharmony_ci{ 15962306a36Sopenharmony_ci struct mpls_entry_decoded dec; 16062306a36Sopenharmony_ci unsigned int mpls_hdr_len = 0; 16162306a36Sopenharmony_ci struct mpls_shim_hdr *hdr; 16262306a36Sopenharmony_ci bool eli_seen = false; 16362306a36Sopenharmony_ci int label_index; 16462306a36Sopenharmony_ci u32 hash = 0; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci for (label_index = 0; label_index < MAX_MP_SELECT_LABELS; 16762306a36Sopenharmony_ci label_index++) { 16862306a36Sopenharmony_ci mpls_hdr_len += sizeof(*hdr); 16962306a36Sopenharmony_ci if (!pskb_may_pull(skb, mpls_hdr_len)) 17062306a36Sopenharmony_ci break; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci /* Read and decode the current label */ 17362306a36Sopenharmony_ci hdr = mpls_hdr(skb) + label_index; 17462306a36Sopenharmony_ci dec = mpls_entry_decode(hdr); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci /* RFC6790 - reserved labels MUST NOT be used as keys 17762306a36Sopenharmony_ci * for the load-balancing function 17862306a36Sopenharmony_ci */ 17962306a36Sopenharmony_ci if (likely(dec.label >= MPLS_LABEL_FIRST_UNRESERVED)) { 18062306a36Sopenharmony_ci hash = jhash_1word(dec.label, hash); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* The entropy label follows the entropy label 18362306a36Sopenharmony_ci * indicator, so this means that the entropy 18462306a36Sopenharmony_ci * label was just added to the hash - no need to 18562306a36Sopenharmony_ci * go any deeper either in the label stack or in the 18662306a36Sopenharmony_ci * payload 18762306a36Sopenharmony_ci */ 18862306a36Sopenharmony_ci if (eli_seen) 18962306a36Sopenharmony_ci break; 19062306a36Sopenharmony_ci } else if (dec.label == MPLS_LABEL_ENTROPY) { 19162306a36Sopenharmony_ci eli_seen = true; 19262306a36Sopenharmony_ci } 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci if (!dec.bos) 19562306a36Sopenharmony_ci continue; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci /* found bottom label; does skb have room for a header? */ 19862306a36Sopenharmony_ci if (pskb_may_pull(skb, mpls_hdr_len + sizeof(struct iphdr))) { 19962306a36Sopenharmony_ci const struct iphdr *v4hdr; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci v4hdr = (const struct iphdr *)(hdr + 1); 20262306a36Sopenharmony_ci if (v4hdr->version == 4) { 20362306a36Sopenharmony_ci hash = jhash_3words(ntohl(v4hdr->saddr), 20462306a36Sopenharmony_ci ntohl(v4hdr->daddr), 20562306a36Sopenharmony_ci v4hdr->protocol, hash); 20662306a36Sopenharmony_ci } else if (v4hdr->version == 6 && 20762306a36Sopenharmony_ci pskb_may_pull(skb, mpls_hdr_len + 20862306a36Sopenharmony_ci sizeof(struct ipv6hdr))) { 20962306a36Sopenharmony_ci const struct ipv6hdr *v6hdr; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci v6hdr = (const struct ipv6hdr *)(hdr + 1); 21262306a36Sopenharmony_ci hash = __ipv6_addr_jhash(&v6hdr->saddr, hash); 21362306a36Sopenharmony_ci hash = __ipv6_addr_jhash(&v6hdr->daddr, hash); 21462306a36Sopenharmony_ci hash = jhash_1word(v6hdr->nexthdr, hash); 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci break; 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci return hash; 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cistatic struct mpls_nh *mpls_get_nexthop(struct mpls_route *rt, u8 index) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci return (struct mpls_nh *)((u8 *)rt->rt_nh + index * rt->rt_nh_size); 22762306a36Sopenharmony_ci} 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci/* number of alive nexthops (rt->rt_nhn_alive) and the flags for 23062306a36Sopenharmony_ci * a next hop (nh->nh_flags) are modified by netdev event handlers. 23162306a36Sopenharmony_ci * Since those fields can change at any moment, use READ_ONCE to 23262306a36Sopenharmony_ci * access both. 23362306a36Sopenharmony_ci */ 23462306a36Sopenharmony_cistatic const struct mpls_nh *mpls_select_multipath(struct mpls_route *rt, 23562306a36Sopenharmony_ci struct sk_buff *skb) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci u32 hash = 0; 23862306a36Sopenharmony_ci int nh_index = 0; 23962306a36Sopenharmony_ci int n = 0; 24062306a36Sopenharmony_ci u8 alive; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci /* No need to look further into packet if there's only 24362306a36Sopenharmony_ci * one path 24462306a36Sopenharmony_ci */ 24562306a36Sopenharmony_ci if (rt->rt_nhn == 1) 24662306a36Sopenharmony_ci return rt->rt_nh; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci alive = READ_ONCE(rt->rt_nhn_alive); 24962306a36Sopenharmony_ci if (alive == 0) 25062306a36Sopenharmony_ci return NULL; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci hash = mpls_multipath_hash(rt, skb); 25362306a36Sopenharmony_ci nh_index = hash % alive; 25462306a36Sopenharmony_ci if (alive == rt->rt_nhn) 25562306a36Sopenharmony_ci goto out; 25662306a36Sopenharmony_ci for_nexthops(rt) { 25762306a36Sopenharmony_ci unsigned int nh_flags = READ_ONCE(nh->nh_flags); 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci if (nh_flags & (RTNH_F_DEAD | RTNH_F_LINKDOWN)) 26062306a36Sopenharmony_ci continue; 26162306a36Sopenharmony_ci if (n == nh_index) 26262306a36Sopenharmony_ci return nh; 26362306a36Sopenharmony_ci n++; 26462306a36Sopenharmony_ci } endfor_nexthops(rt); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ciout: 26762306a36Sopenharmony_ci return mpls_get_nexthop(rt, nh_index); 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cistatic bool mpls_egress(struct net *net, struct mpls_route *rt, 27162306a36Sopenharmony_ci struct sk_buff *skb, struct mpls_entry_decoded dec) 27262306a36Sopenharmony_ci{ 27362306a36Sopenharmony_ci enum mpls_payload_type payload_type; 27462306a36Sopenharmony_ci bool success = false; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci /* The IPv4 code below accesses through the IPv4 header 27762306a36Sopenharmony_ci * checksum, which is 12 bytes into the packet. 27862306a36Sopenharmony_ci * The IPv6 code below accesses through the IPv6 hop limit 27962306a36Sopenharmony_ci * which is 8 bytes into the packet. 28062306a36Sopenharmony_ci * 28162306a36Sopenharmony_ci * For all supported cases there should always be at least 12 28262306a36Sopenharmony_ci * bytes of packet data present. The IPv4 header is 20 bytes 28362306a36Sopenharmony_ci * without options and the IPv6 header is always 40 bytes 28462306a36Sopenharmony_ci * long. 28562306a36Sopenharmony_ci */ 28662306a36Sopenharmony_ci if (!pskb_may_pull(skb, 12)) 28762306a36Sopenharmony_ci return false; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci payload_type = rt->rt_payload_type; 29062306a36Sopenharmony_ci if (payload_type == MPT_UNSPEC) 29162306a36Sopenharmony_ci payload_type = ip_hdr(skb)->version; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci switch (payload_type) { 29462306a36Sopenharmony_ci case MPT_IPV4: { 29562306a36Sopenharmony_ci struct iphdr *hdr4 = ip_hdr(skb); 29662306a36Sopenharmony_ci u8 new_ttl; 29762306a36Sopenharmony_ci skb->protocol = htons(ETH_P_IP); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci /* If propagating TTL, take the decremented TTL from 30062306a36Sopenharmony_ci * the incoming MPLS header, otherwise decrement the 30162306a36Sopenharmony_ci * TTL, but only if not 0 to avoid underflow. 30262306a36Sopenharmony_ci */ 30362306a36Sopenharmony_ci if (rt->rt_ttl_propagate == MPLS_TTL_PROP_ENABLED || 30462306a36Sopenharmony_ci (rt->rt_ttl_propagate == MPLS_TTL_PROP_DEFAULT && 30562306a36Sopenharmony_ci net->mpls.ip_ttl_propagate)) 30662306a36Sopenharmony_ci new_ttl = dec.ttl; 30762306a36Sopenharmony_ci else 30862306a36Sopenharmony_ci new_ttl = hdr4->ttl ? hdr4->ttl - 1 : 0; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci csum_replace2(&hdr4->check, 31162306a36Sopenharmony_ci htons(hdr4->ttl << 8), 31262306a36Sopenharmony_ci htons(new_ttl << 8)); 31362306a36Sopenharmony_ci hdr4->ttl = new_ttl; 31462306a36Sopenharmony_ci success = true; 31562306a36Sopenharmony_ci break; 31662306a36Sopenharmony_ci } 31762306a36Sopenharmony_ci case MPT_IPV6: { 31862306a36Sopenharmony_ci struct ipv6hdr *hdr6 = ipv6_hdr(skb); 31962306a36Sopenharmony_ci skb->protocol = htons(ETH_P_IPV6); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci /* If propagating TTL, take the decremented TTL from 32262306a36Sopenharmony_ci * the incoming MPLS header, otherwise decrement the 32362306a36Sopenharmony_ci * hop limit, but only if not 0 to avoid underflow. 32462306a36Sopenharmony_ci */ 32562306a36Sopenharmony_ci if (rt->rt_ttl_propagate == MPLS_TTL_PROP_ENABLED || 32662306a36Sopenharmony_ci (rt->rt_ttl_propagate == MPLS_TTL_PROP_DEFAULT && 32762306a36Sopenharmony_ci net->mpls.ip_ttl_propagate)) 32862306a36Sopenharmony_ci hdr6->hop_limit = dec.ttl; 32962306a36Sopenharmony_ci else if (hdr6->hop_limit) 33062306a36Sopenharmony_ci hdr6->hop_limit = hdr6->hop_limit - 1; 33162306a36Sopenharmony_ci success = true; 33262306a36Sopenharmony_ci break; 33362306a36Sopenharmony_ci } 33462306a36Sopenharmony_ci case MPT_UNSPEC: 33562306a36Sopenharmony_ci /* Should have decided which protocol it is by now */ 33662306a36Sopenharmony_ci break; 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci return success; 34062306a36Sopenharmony_ci} 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_cistatic int mpls_forward(struct sk_buff *skb, struct net_device *dev, 34362306a36Sopenharmony_ci struct packet_type *pt, struct net_device *orig_dev) 34462306a36Sopenharmony_ci{ 34562306a36Sopenharmony_ci struct net *net = dev_net(dev); 34662306a36Sopenharmony_ci struct mpls_shim_hdr *hdr; 34762306a36Sopenharmony_ci const struct mpls_nh *nh; 34862306a36Sopenharmony_ci struct mpls_route *rt; 34962306a36Sopenharmony_ci struct mpls_entry_decoded dec; 35062306a36Sopenharmony_ci struct net_device *out_dev; 35162306a36Sopenharmony_ci struct mpls_dev *out_mdev; 35262306a36Sopenharmony_ci struct mpls_dev *mdev; 35362306a36Sopenharmony_ci unsigned int hh_len; 35462306a36Sopenharmony_ci unsigned int new_header_size; 35562306a36Sopenharmony_ci unsigned int mtu; 35662306a36Sopenharmony_ci int err; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci /* Careful this entire function runs inside of an rcu critical section */ 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci mdev = mpls_dev_get(dev); 36162306a36Sopenharmony_ci if (!mdev) 36262306a36Sopenharmony_ci goto drop; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci MPLS_INC_STATS_LEN(mdev, skb->len, rx_packets, 36562306a36Sopenharmony_ci rx_bytes); 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci if (!mdev->input_enabled) { 36862306a36Sopenharmony_ci MPLS_INC_STATS(mdev, rx_dropped); 36962306a36Sopenharmony_ci goto drop; 37062306a36Sopenharmony_ci } 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci if (skb->pkt_type != PACKET_HOST) 37362306a36Sopenharmony_ci goto err; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) 37662306a36Sopenharmony_ci goto err; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci if (!pskb_may_pull(skb, sizeof(*hdr))) 37962306a36Sopenharmony_ci goto err; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci skb_dst_drop(skb); 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci /* Read and decode the label */ 38462306a36Sopenharmony_ci hdr = mpls_hdr(skb); 38562306a36Sopenharmony_ci dec = mpls_entry_decode(hdr); 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci rt = mpls_route_input_rcu(net, dec.label); 38862306a36Sopenharmony_ci if (!rt) { 38962306a36Sopenharmony_ci MPLS_INC_STATS(mdev, rx_noroute); 39062306a36Sopenharmony_ci goto drop; 39162306a36Sopenharmony_ci } 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci nh = mpls_select_multipath(rt, skb); 39462306a36Sopenharmony_ci if (!nh) 39562306a36Sopenharmony_ci goto err; 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci /* Pop the label */ 39862306a36Sopenharmony_ci skb_pull(skb, sizeof(*hdr)); 39962306a36Sopenharmony_ci skb_reset_network_header(skb); 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci skb_orphan(skb); 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci if (skb_warn_if_lro(skb)) 40462306a36Sopenharmony_ci goto err; 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci skb_forward_csum(skb); 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci /* Verify ttl is valid */ 40962306a36Sopenharmony_ci if (dec.ttl <= 1) 41062306a36Sopenharmony_ci goto err; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci /* Find the output device */ 41362306a36Sopenharmony_ci out_dev = nh->nh_dev; 41462306a36Sopenharmony_ci if (!mpls_output_possible(out_dev)) 41562306a36Sopenharmony_ci goto tx_err; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci /* Verify the destination can hold the packet */ 41862306a36Sopenharmony_ci new_header_size = mpls_nh_header_size(nh); 41962306a36Sopenharmony_ci mtu = mpls_dev_mtu(out_dev); 42062306a36Sopenharmony_ci if (mpls_pkt_too_big(skb, mtu - new_header_size)) 42162306a36Sopenharmony_ci goto tx_err; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci hh_len = LL_RESERVED_SPACE(out_dev); 42462306a36Sopenharmony_ci if (!out_dev->header_ops) 42562306a36Sopenharmony_ci hh_len = 0; 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci /* Ensure there is enough space for the headers in the skb */ 42862306a36Sopenharmony_ci if (skb_cow(skb, hh_len + new_header_size)) 42962306a36Sopenharmony_ci goto tx_err; 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci skb->dev = out_dev; 43262306a36Sopenharmony_ci skb->protocol = htons(ETH_P_MPLS_UC); 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci dec.ttl -= 1; 43562306a36Sopenharmony_ci if (unlikely(!new_header_size && dec.bos)) { 43662306a36Sopenharmony_ci /* Penultimate hop popping */ 43762306a36Sopenharmony_ci if (!mpls_egress(dev_net(out_dev), rt, skb, dec)) 43862306a36Sopenharmony_ci goto err; 43962306a36Sopenharmony_ci } else { 44062306a36Sopenharmony_ci bool bos; 44162306a36Sopenharmony_ci int i; 44262306a36Sopenharmony_ci skb_push(skb, new_header_size); 44362306a36Sopenharmony_ci skb_reset_network_header(skb); 44462306a36Sopenharmony_ci /* Push the new labels */ 44562306a36Sopenharmony_ci hdr = mpls_hdr(skb); 44662306a36Sopenharmony_ci bos = dec.bos; 44762306a36Sopenharmony_ci for (i = nh->nh_labels - 1; i >= 0; i--) { 44862306a36Sopenharmony_ci hdr[i] = mpls_entry_encode(nh->nh_label[i], 44962306a36Sopenharmony_ci dec.ttl, 0, bos); 45062306a36Sopenharmony_ci bos = false; 45162306a36Sopenharmony_ci } 45262306a36Sopenharmony_ci } 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci mpls_stats_inc_outucastpkts(out_dev, skb); 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci /* If via wasn't specified then send out using device address */ 45762306a36Sopenharmony_ci if (nh->nh_via_table == MPLS_NEIGH_TABLE_UNSPEC) 45862306a36Sopenharmony_ci err = neigh_xmit(NEIGH_LINK_TABLE, out_dev, 45962306a36Sopenharmony_ci out_dev->dev_addr, skb); 46062306a36Sopenharmony_ci else 46162306a36Sopenharmony_ci err = neigh_xmit(nh->nh_via_table, out_dev, 46262306a36Sopenharmony_ci mpls_nh_via(rt, nh), skb); 46362306a36Sopenharmony_ci if (err) 46462306a36Sopenharmony_ci net_dbg_ratelimited("%s: packet transmission failed: %d\n", 46562306a36Sopenharmony_ci __func__, err); 46662306a36Sopenharmony_ci return 0; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_citx_err: 46962306a36Sopenharmony_ci out_mdev = out_dev ? mpls_dev_get(out_dev) : NULL; 47062306a36Sopenharmony_ci if (out_mdev) 47162306a36Sopenharmony_ci MPLS_INC_STATS(out_mdev, tx_errors); 47262306a36Sopenharmony_ci goto drop; 47362306a36Sopenharmony_cierr: 47462306a36Sopenharmony_ci MPLS_INC_STATS(mdev, rx_errors); 47562306a36Sopenharmony_cidrop: 47662306a36Sopenharmony_ci kfree_skb(skb); 47762306a36Sopenharmony_ci return NET_RX_DROP; 47862306a36Sopenharmony_ci} 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_cistatic struct packet_type mpls_packet_type __read_mostly = { 48162306a36Sopenharmony_ci .type = cpu_to_be16(ETH_P_MPLS_UC), 48262306a36Sopenharmony_ci .func = mpls_forward, 48362306a36Sopenharmony_ci}; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_cistatic const struct nla_policy rtm_mpls_policy[RTA_MAX+1] = { 48662306a36Sopenharmony_ci [RTA_DST] = { .type = NLA_U32 }, 48762306a36Sopenharmony_ci [RTA_OIF] = { .type = NLA_U32 }, 48862306a36Sopenharmony_ci [RTA_TTL_PROPAGATE] = { .type = NLA_U8 }, 48962306a36Sopenharmony_ci}; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_cistruct mpls_route_config { 49262306a36Sopenharmony_ci u32 rc_protocol; 49362306a36Sopenharmony_ci u32 rc_ifindex; 49462306a36Sopenharmony_ci u8 rc_via_table; 49562306a36Sopenharmony_ci u8 rc_via_alen; 49662306a36Sopenharmony_ci u8 rc_via[MAX_VIA_ALEN]; 49762306a36Sopenharmony_ci u32 rc_label; 49862306a36Sopenharmony_ci u8 rc_ttl_propagate; 49962306a36Sopenharmony_ci u8 rc_output_labels; 50062306a36Sopenharmony_ci u32 rc_output_label[MAX_NEW_LABELS]; 50162306a36Sopenharmony_ci u32 rc_nlflags; 50262306a36Sopenharmony_ci enum mpls_payload_type rc_payload_type; 50362306a36Sopenharmony_ci struct nl_info rc_nlinfo; 50462306a36Sopenharmony_ci struct rtnexthop *rc_mp; 50562306a36Sopenharmony_ci int rc_mp_len; 50662306a36Sopenharmony_ci}; 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci/* all nexthops within a route have the same size based on max 50962306a36Sopenharmony_ci * number of labels and max via length for a hop 51062306a36Sopenharmony_ci */ 51162306a36Sopenharmony_cistatic struct mpls_route *mpls_rt_alloc(u8 num_nh, u8 max_alen, u8 max_labels) 51262306a36Sopenharmony_ci{ 51362306a36Sopenharmony_ci u8 nh_size = MPLS_NH_SIZE(max_labels, max_alen); 51462306a36Sopenharmony_ci struct mpls_route *rt; 51562306a36Sopenharmony_ci size_t size; 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci size = sizeof(*rt) + num_nh * nh_size; 51862306a36Sopenharmony_ci if (size > MAX_MPLS_ROUTE_MEM) 51962306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci rt = kzalloc(size, GFP_KERNEL); 52262306a36Sopenharmony_ci if (!rt) 52362306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci rt->rt_nhn = num_nh; 52662306a36Sopenharmony_ci rt->rt_nhn_alive = num_nh; 52762306a36Sopenharmony_ci rt->rt_nh_size = nh_size; 52862306a36Sopenharmony_ci rt->rt_via_offset = MPLS_NH_VIA_OFF(max_labels); 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci return rt; 53162306a36Sopenharmony_ci} 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_cistatic void mpls_rt_free(struct mpls_route *rt) 53462306a36Sopenharmony_ci{ 53562306a36Sopenharmony_ci if (rt) 53662306a36Sopenharmony_ci kfree_rcu(rt, rt_rcu); 53762306a36Sopenharmony_ci} 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_cistatic void mpls_notify_route(struct net *net, unsigned index, 54062306a36Sopenharmony_ci struct mpls_route *old, struct mpls_route *new, 54162306a36Sopenharmony_ci const struct nl_info *info) 54262306a36Sopenharmony_ci{ 54362306a36Sopenharmony_ci struct nlmsghdr *nlh = info ? info->nlh : NULL; 54462306a36Sopenharmony_ci unsigned portid = info ? info->portid : 0; 54562306a36Sopenharmony_ci int event = new ? RTM_NEWROUTE : RTM_DELROUTE; 54662306a36Sopenharmony_ci struct mpls_route *rt = new ? new : old; 54762306a36Sopenharmony_ci unsigned nlm_flags = (old && new) ? NLM_F_REPLACE : 0; 54862306a36Sopenharmony_ci /* Ignore reserved labels for now */ 54962306a36Sopenharmony_ci if (rt && (index >= MPLS_LABEL_FIRST_UNRESERVED)) 55062306a36Sopenharmony_ci rtmsg_lfib(event, index, rt, nlh, net, portid, nlm_flags); 55162306a36Sopenharmony_ci} 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_cistatic void mpls_route_update(struct net *net, unsigned index, 55462306a36Sopenharmony_ci struct mpls_route *new, 55562306a36Sopenharmony_ci const struct nl_info *info) 55662306a36Sopenharmony_ci{ 55762306a36Sopenharmony_ci struct mpls_route __rcu **platform_label; 55862306a36Sopenharmony_ci struct mpls_route *rt; 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_ci ASSERT_RTNL(); 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci platform_label = rtnl_dereference(net->mpls.platform_label); 56362306a36Sopenharmony_ci rt = rtnl_dereference(platform_label[index]); 56462306a36Sopenharmony_ci rcu_assign_pointer(platform_label[index], new); 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci mpls_notify_route(net, index, rt, new, info); 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci /* If we removed a route free it now */ 56962306a36Sopenharmony_ci mpls_rt_free(rt); 57062306a36Sopenharmony_ci} 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_cistatic unsigned find_free_label(struct net *net) 57362306a36Sopenharmony_ci{ 57462306a36Sopenharmony_ci struct mpls_route __rcu **platform_label; 57562306a36Sopenharmony_ci size_t platform_labels; 57662306a36Sopenharmony_ci unsigned index; 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci platform_label = rtnl_dereference(net->mpls.platform_label); 57962306a36Sopenharmony_ci platform_labels = net->mpls.platform_labels; 58062306a36Sopenharmony_ci for (index = MPLS_LABEL_FIRST_UNRESERVED; index < platform_labels; 58162306a36Sopenharmony_ci index++) { 58262306a36Sopenharmony_ci if (!rtnl_dereference(platform_label[index])) 58362306a36Sopenharmony_ci return index; 58462306a36Sopenharmony_ci } 58562306a36Sopenharmony_ci return LABEL_NOT_SPECIFIED; 58662306a36Sopenharmony_ci} 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_INET) 58962306a36Sopenharmony_cistatic struct net_device *inet_fib_lookup_dev(struct net *net, 59062306a36Sopenharmony_ci const void *addr) 59162306a36Sopenharmony_ci{ 59262306a36Sopenharmony_ci struct net_device *dev; 59362306a36Sopenharmony_ci struct rtable *rt; 59462306a36Sopenharmony_ci struct in_addr daddr; 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci memcpy(&daddr, addr, sizeof(struct in_addr)); 59762306a36Sopenharmony_ci rt = ip_route_output(net, daddr.s_addr, 0, 0, 0); 59862306a36Sopenharmony_ci if (IS_ERR(rt)) 59962306a36Sopenharmony_ci return ERR_CAST(rt); 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci dev = rt->dst.dev; 60262306a36Sopenharmony_ci dev_hold(dev); 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci ip_rt_put(rt); 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci return dev; 60762306a36Sopenharmony_ci} 60862306a36Sopenharmony_ci#else 60962306a36Sopenharmony_cistatic struct net_device *inet_fib_lookup_dev(struct net *net, 61062306a36Sopenharmony_ci const void *addr) 61162306a36Sopenharmony_ci{ 61262306a36Sopenharmony_ci return ERR_PTR(-EAFNOSUPPORT); 61362306a36Sopenharmony_ci} 61462306a36Sopenharmony_ci#endif 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6) 61762306a36Sopenharmony_cistatic struct net_device *inet6_fib_lookup_dev(struct net *net, 61862306a36Sopenharmony_ci const void *addr) 61962306a36Sopenharmony_ci{ 62062306a36Sopenharmony_ci struct net_device *dev; 62162306a36Sopenharmony_ci struct dst_entry *dst; 62262306a36Sopenharmony_ci struct flowi6 fl6; 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci if (!ipv6_stub) 62562306a36Sopenharmony_ci return ERR_PTR(-EAFNOSUPPORT); 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci memset(&fl6, 0, sizeof(fl6)); 62862306a36Sopenharmony_ci memcpy(&fl6.daddr, addr, sizeof(struct in6_addr)); 62962306a36Sopenharmony_ci dst = ipv6_stub->ipv6_dst_lookup_flow(net, NULL, &fl6, NULL); 63062306a36Sopenharmony_ci if (IS_ERR(dst)) 63162306a36Sopenharmony_ci return ERR_CAST(dst); 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci dev = dst->dev; 63462306a36Sopenharmony_ci dev_hold(dev); 63562306a36Sopenharmony_ci dst_release(dst); 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci return dev; 63862306a36Sopenharmony_ci} 63962306a36Sopenharmony_ci#else 64062306a36Sopenharmony_cistatic struct net_device *inet6_fib_lookup_dev(struct net *net, 64162306a36Sopenharmony_ci const void *addr) 64262306a36Sopenharmony_ci{ 64362306a36Sopenharmony_ci return ERR_PTR(-EAFNOSUPPORT); 64462306a36Sopenharmony_ci} 64562306a36Sopenharmony_ci#endif 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_cistatic struct net_device *find_outdev(struct net *net, 64862306a36Sopenharmony_ci struct mpls_route *rt, 64962306a36Sopenharmony_ci struct mpls_nh *nh, int oif) 65062306a36Sopenharmony_ci{ 65162306a36Sopenharmony_ci struct net_device *dev = NULL; 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci if (!oif) { 65462306a36Sopenharmony_ci switch (nh->nh_via_table) { 65562306a36Sopenharmony_ci case NEIGH_ARP_TABLE: 65662306a36Sopenharmony_ci dev = inet_fib_lookup_dev(net, mpls_nh_via(rt, nh)); 65762306a36Sopenharmony_ci break; 65862306a36Sopenharmony_ci case NEIGH_ND_TABLE: 65962306a36Sopenharmony_ci dev = inet6_fib_lookup_dev(net, mpls_nh_via(rt, nh)); 66062306a36Sopenharmony_ci break; 66162306a36Sopenharmony_ci case NEIGH_LINK_TABLE: 66262306a36Sopenharmony_ci break; 66362306a36Sopenharmony_ci } 66462306a36Sopenharmony_ci } else { 66562306a36Sopenharmony_ci dev = dev_get_by_index(net, oif); 66662306a36Sopenharmony_ci } 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci if (!dev) 66962306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci if (IS_ERR(dev)) 67262306a36Sopenharmony_ci return dev; 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci /* The caller is holding rtnl anyways, so release the dev reference */ 67562306a36Sopenharmony_ci dev_put(dev); 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci return dev; 67862306a36Sopenharmony_ci} 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_cistatic int mpls_nh_assign_dev(struct net *net, struct mpls_route *rt, 68162306a36Sopenharmony_ci struct mpls_nh *nh, int oif) 68262306a36Sopenharmony_ci{ 68362306a36Sopenharmony_ci struct net_device *dev = NULL; 68462306a36Sopenharmony_ci int err = -ENODEV; 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci dev = find_outdev(net, rt, nh, oif); 68762306a36Sopenharmony_ci if (IS_ERR(dev)) { 68862306a36Sopenharmony_ci err = PTR_ERR(dev); 68962306a36Sopenharmony_ci dev = NULL; 69062306a36Sopenharmony_ci goto errout; 69162306a36Sopenharmony_ci } 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci /* Ensure this is a supported device */ 69462306a36Sopenharmony_ci err = -EINVAL; 69562306a36Sopenharmony_ci if (!mpls_dev_get(dev)) 69662306a36Sopenharmony_ci goto errout; 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci if ((nh->nh_via_table == NEIGH_LINK_TABLE) && 69962306a36Sopenharmony_ci (dev->addr_len != nh->nh_via_alen)) 70062306a36Sopenharmony_ci goto errout; 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_ci nh->nh_dev = dev; 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_ci if (!(dev->flags & IFF_UP)) { 70562306a36Sopenharmony_ci nh->nh_flags |= RTNH_F_DEAD; 70662306a36Sopenharmony_ci } else { 70762306a36Sopenharmony_ci unsigned int flags; 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci flags = dev_get_flags(dev); 71062306a36Sopenharmony_ci if (!(flags & (IFF_RUNNING | IFF_LOWER_UP))) 71162306a36Sopenharmony_ci nh->nh_flags |= RTNH_F_LINKDOWN; 71262306a36Sopenharmony_ci } 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci return 0; 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_cierrout: 71762306a36Sopenharmony_ci return err; 71862306a36Sopenharmony_ci} 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_cistatic int nla_get_via(const struct nlattr *nla, u8 *via_alen, u8 *via_table, 72162306a36Sopenharmony_ci u8 via_addr[], struct netlink_ext_ack *extack) 72262306a36Sopenharmony_ci{ 72362306a36Sopenharmony_ci struct rtvia *via = nla_data(nla); 72462306a36Sopenharmony_ci int err = -EINVAL; 72562306a36Sopenharmony_ci int alen; 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci if (nla_len(nla) < offsetof(struct rtvia, rtvia_addr)) { 72862306a36Sopenharmony_ci NL_SET_ERR_MSG_ATTR(extack, nla, 72962306a36Sopenharmony_ci "Invalid attribute length for RTA_VIA"); 73062306a36Sopenharmony_ci goto errout; 73162306a36Sopenharmony_ci } 73262306a36Sopenharmony_ci alen = nla_len(nla) - 73362306a36Sopenharmony_ci offsetof(struct rtvia, rtvia_addr); 73462306a36Sopenharmony_ci if (alen > MAX_VIA_ALEN) { 73562306a36Sopenharmony_ci NL_SET_ERR_MSG_ATTR(extack, nla, 73662306a36Sopenharmony_ci "Invalid address length for RTA_VIA"); 73762306a36Sopenharmony_ci goto errout; 73862306a36Sopenharmony_ci } 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_ci /* Validate the address family */ 74162306a36Sopenharmony_ci switch (via->rtvia_family) { 74262306a36Sopenharmony_ci case AF_PACKET: 74362306a36Sopenharmony_ci *via_table = NEIGH_LINK_TABLE; 74462306a36Sopenharmony_ci break; 74562306a36Sopenharmony_ci case AF_INET: 74662306a36Sopenharmony_ci *via_table = NEIGH_ARP_TABLE; 74762306a36Sopenharmony_ci if (alen != 4) 74862306a36Sopenharmony_ci goto errout; 74962306a36Sopenharmony_ci break; 75062306a36Sopenharmony_ci case AF_INET6: 75162306a36Sopenharmony_ci *via_table = NEIGH_ND_TABLE; 75262306a36Sopenharmony_ci if (alen != 16) 75362306a36Sopenharmony_ci goto errout; 75462306a36Sopenharmony_ci break; 75562306a36Sopenharmony_ci default: 75662306a36Sopenharmony_ci /* Unsupported address family */ 75762306a36Sopenharmony_ci goto errout; 75862306a36Sopenharmony_ci } 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci memcpy(via_addr, via->rtvia_addr, alen); 76162306a36Sopenharmony_ci *via_alen = alen; 76262306a36Sopenharmony_ci err = 0; 76362306a36Sopenharmony_ci 76462306a36Sopenharmony_cierrout: 76562306a36Sopenharmony_ci return err; 76662306a36Sopenharmony_ci} 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_cistatic int mpls_nh_build_from_cfg(struct mpls_route_config *cfg, 76962306a36Sopenharmony_ci struct mpls_route *rt) 77062306a36Sopenharmony_ci{ 77162306a36Sopenharmony_ci struct net *net = cfg->rc_nlinfo.nl_net; 77262306a36Sopenharmony_ci struct mpls_nh *nh = rt->rt_nh; 77362306a36Sopenharmony_ci int err; 77462306a36Sopenharmony_ci int i; 77562306a36Sopenharmony_ci 77662306a36Sopenharmony_ci if (!nh) 77762306a36Sopenharmony_ci return -ENOMEM; 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci nh->nh_labels = cfg->rc_output_labels; 78062306a36Sopenharmony_ci for (i = 0; i < nh->nh_labels; i++) 78162306a36Sopenharmony_ci nh->nh_label[i] = cfg->rc_output_label[i]; 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_ci nh->nh_via_table = cfg->rc_via_table; 78462306a36Sopenharmony_ci memcpy(__mpls_nh_via(rt, nh), cfg->rc_via, cfg->rc_via_alen); 78562306a36Sopenharmony_ci nh->nh_via_alen = cfg->rc_via_alen; 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ci err = mpls_nh_assign_dev(net, rt, nh, cfg->rc_ifindex); 78862306a36Sopenharmony_ci if (err) 78962306a36Sopenharmony_ci goto errout; 79062306a36Sopenharmony_ci 79162306a36Sopenharmony_ci if (nh->nh_flags & (RTNH_F_DEAD | RTNH_F_LINKDOWN)) 79262306a36Sopenharmony_ci rt->rt_nhn_alive--; 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci return 0; 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_cierrout: 79762306a36Sopenharmony_ci return err; 79862306a36Sopenharmony_ci} 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_cistatic int mpls_nh_build(struct net *net, struct mpls_route *rt, 80162306a36Sopenharmony_ci struct mpls_nh *nh, int oif, struct nlattr *via, 80262306a36Sopenharmony_ci struct nlattr *newdst, u8 max_labels, 80362306a36Sopenharmony_ci struct netlink_ext_ack *extack) 80462306a36Sopenharmony_ci{ 80562306a36Sopenharmony_ci int err = -ENOMEM; 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci if (!nh) 80862306a36Sopenharmony_ci goto errout; 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_ci if (newdst) { 81162306a36Sopenharmony_ci err = nla_get_labels(newdst, max_labels, &nh->nh_labels, 81262306a36Sopenharmony_ci nh->nh_label, extack); 81362306a36Sopenharmony_ci if (err) 81462306a36Sopenharmony_ci goto errout; 81562306a36Sopenharmony_ci } 81662306a36Sopenharmony_ci 81762306a36Sopenharmony_ci if (via) { 81862306a36Sopenharmony_ci err = nla_get_via(via, &nh->nh_via_alen, &nh->nh_via_table, 81962306a36Sopenharmony_ci __mpls_nh_via(rt, nh), extack); 82062306a36Sopenharmony_ci if (err) 82162306a36Sopenharmony_ci goto errout; 82262306a36Sopenharmony_ci } else { 82362306a36Sopenharmony_ci nh->nh_via_table = MPLS_NEIGH_TABLE_UNSPEC; 82462306a36Sopenharmony_ci } 82562306a36Sopenharmony_ci 82662306a36Sopenharmony_ci err = mpls_nh_assign_dev(net, rt, nh, oif); 82762306a36Sopenharmony_ci if (err) 82862306a36Sopenharmony_ci goto errout; 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_ci return 0; 83162306a36Sopenharmony_ci 83262306a36Sopenharmony_cierrout: 83362306a36Sopenharmony_ci return err; 83462306a36Sopenharmony_ci} 83562306a36Sopenharmony_ci 83662306a36Sopenharmony_cistatic u8 mpls_count_nexthops(struct rtnexthop *rtnh, int len, 83762306a36Sopenharmony_ci u8 cfg_via_alen, u8 *max_via_alen, 83862306a36Sopenharmony_ci u8 *max_labels) 83962306a36Sopenharmony_ci{ 84062306a36Sopenharmony_ci int remaining = len; 84162306a36Sopenharmony_ci u8 nhs = 0; 84262306a36Sopenharmony_ci 84362306a36Sopenharmony_ci *max_via_alen = 0; 84462306a36Sopenharmony_ci *max_labels = 0; 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci while (rtnh_ok(rtnh, remaining)) { 84762306a36Sopenharmony_ci struct nlattr *nla, *attrs = rtnh_attrs(rtnh); 84862306a36Sopenharmony_ci int attrlen; 84962306a36Sopenharmony_ci u8 n_labels = 0; 85062306a36Sopenharmony_ci 85162306a36Sopenharmony_ci attrlen = rtnh_attrlen(rtnh); 85262306a36Sopenharmony_ci nla = nla_find(attrs, attrlen, RTA_VIA); 85362306a36Sopenharmony_ci if (nla && nla_len(nla) >= 85462306a36Sopenharmony_ci offsetof(struct rtvia, rtvia_addr)) { 85562306a36Sopenharmony_ci int via_alen = nla_len(nla) - 85662306a36Sopenharmony_ci offsetof(struct rtvia, rtvia_addr); 85762306a36Sopenharmony_ci 85862306a36Sopenharmony_ci if (via_alen <= MAX_VIA_ALEN) 85962306a36Sopenharmony_ci *max_via_alen = max_t(u16, *max_via_alen, 86062306a36Sopenharmony_ci via_alen); 86162306a36Sopenharmony_ci } 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_ci nla = nla_find(attrs, attrlen, RTA_NEWDST); 86462306a36Sopenharmony_ci if (nla && 86562306a36Sopenharmony_ci nla_get_labels(nla, MAX_NEW_LABELS, &n_labels, 86662306a36Sopenharmony_ci NULL, NULL) != 0) 86762306a36Sopenharmony_ci return 0; 86862306a36Sopenharmony_ci 86962306a36Sopenharmony_ci *max_labels = max_t(u8, *max_labels, n_labels); 87062306a36Sopenharmony_ci 87162306a36Sopenharmony_ci /* number of nexthops is tracked by a u8. 87262306a36Sopenharmony_ci * Check for overflow. 87362306a36Sopenharmony_ci */ 87462306a36Sopenharmony_ci if (nhs == 255) 87562306a36Sopenharmony_ci return 0; 87662306a36Sopenharmony_ci nhs++; 87762306a36Sopenharmony_ci 87862306a36Sopenharmony_ci rtnh = rtnh_next(rtnh, &remaining); 87962306a36Sopenharmony_ci } 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_ci /* leftover implies invalid nexthop configuration, discard it */ 88262306a36Sopenharmony_ci return remaining > 0 ? 0 : nhs; 88362306a36Sopenharmony_ci} 88462306a36Sopenharmony_ci 88562306a36Sopenharmony_cistatic int mpls_nh_build_multi(struct mpls_route_config *cfg, 88662306a36Sopenharmony_ci struct mpls_route *rt, u8 max_labels, 88762306a36Sopenharmony_ci struct netlink_ext_ack *extack) 88862306a36Sopenharmony_ci{ 88962306a36Sopenharmony_ci struct rtnexthop *rtnh = cfg->rc_mp; 89062306a36Sopenharmony_ci struct nlattr *nla_via, *nla_newdst; 89162306a36Sopenharmony_ci int remaining = cfg->rc_mp_len; 89262306a36Sopenharmony_ci int err = 0; 89362306a36Sopenharmony_ci u8 nhs = 0; 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_ci change_nexthops(rt) { 89662306a36Sopenharmony_ci int attrlen; 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci nla_via = NULL; 89962306a36Sopenharmony_ci nla_newdst = NULL; 90062306a36Sopenharmony_ci 90162306a36Sopenharmony_ci err = -EINVAL; 90262306a36Sopenharmony_ci if (!rtnh_ok(rtnh, remaining)) 90362306a36Sopenharmony_ci goto errout; 90462306a36Sopenharmony_ci 90562306a36Sopenharmony_ci /* neither weighted multipath nor any flags 90662306a36Sopenharmony_ci * are supported 90762306a36Sopenharmony_ci */ 90862306a36Sopenharmony_ci if (rtnh->rtnh_hops || rtnh->rtnh_flags) 90962306a36Sopenharmony_ci goto errout; 91062306a36Sopenharmony_ci 91162306a36Sopenharmony_ci attrlen = rtnh_attrlen(rtnh); 91262306a36Sopenharmony_ci if (attrlen > 0) { 91362306a36Sopenharmony_ci struct nlattr *attrs = rtnh_attrs(rtnh); 91462306a36Sopenharmony_ci 91562306a36Sopenharmony_ci nla_via = nla_find(attrs, attrlen, RTA_VIA); 91662306a36Sopenharmony_ci nla_newdst = nla_find(attrs, attrlen, RTA_NEWDST); 91762306a36Sopenharmony_ci } 91862306a36Sopenharmony_ci 91962306a36Sopenharmony_ci err = mpls_nh_build(cfg->rc_nlinfo.nl_net, rt, nh, 92062306a36Sopenharmony_ci rtnh->rtnh_ifindex, nla_via, nla_newdst, 92162306a36Sopenharmony_ci max_labels, extack); 92262306a36Sopenharmony_ci if (err) 92362306a36Sopenharmony_ci goto errout; 92462306a36Sopenharmony_ci 92562306a36Sopenharmony_ci if (nh->nh_flags & (RTNH_F_DEAD | RTNH_F_LINKDOWN)) 92662306a36Sopenharmony_ci rt->rt_nhn_alive--; 92762306a36Sopenharmony_ci 92862306a36Sopenharmony_ci rtnh = rtnh_next(rtnh, &remaining); 92962306a36Sopenharmony_ci nhs++; 93062306a36Sopenharmony_ci } endfor_nexthops(rt); 93162306a36Sopenharmony_ci 93262306a36Sopenharmony_ci rt->rt_nhn = nhs; 93362306a36Sopenharmony_ci 93462306a36Sopenharmony_ci return 0; 93562306a36Sopenharmony_ci 93662306a36Sopenharmony_cierrout: 93762306a36Sopenharmony_ci return err; 93862306a36Sopenharmony_ci} 93962306a36Sopenharmony_ci 94062306a36Sopenharmony_cistatic bool mpls_label_ok(struct net *net, unsigned int *index, 94162306a36Sopenharmony_ci struct netlink_ext_ack *extack) 94262306a36Sopenharmony_ci{ 94362306a36Sopenharmony_ci bool is_ok = true; 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_ci /* Reserved labels may not be set */ 94662306a36Sopenharmony_ci if (*index < MPLS_LABEL_FIRST_UNRESERVED) { 94762306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 94862306a36Sopenharmony_ci "Invalid label - must be MPLS_LABEL_FIRST_UNRESERVED or higher"); 94962306a36Sopenharmony_ci is_ok = false; 95062306a36Sopenharmony_ci } 95162306a36Sopenharmony_ci 95262306a36Sopenharmony_ci /* The full 20 bit range may not be supported. */ 95362306a36Sopenharmony_ci if (is_ok && *index >= net->mpls.platform_labels) { 95462306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 95562306a36Sopenharmony_ci "Label >= configured maximum in platform_labels"); 95662306a36Sopenharmony_ci is_ok = false; 95762306a36Sopenharmony_ci } 95862306a36Sopenharmony_ci 95962306a36Sopenharmony_ci *index = array_index_nospec(*index, net->mpls.platform_labels); 96062306a36Sopenharmony_ci return is_ok; 96162306a36Sopenharmony_ci} 96262306a36Sopenharmony_ci 96362306a36Sopenharmony_cistatic int mpls_route_add(struct mpls_route_config *cfg, 96462306a36Sopenharmony_ci struct netlink_ext_ack *extack) 96562306a36Sopenharmony_ci{ 96662306a36Sopenharmony_ci struct mpls_route __rcu **platform_label; 96762306a36Sopenharmony_ci struct net *net = cfg->rc_nlinfo.nl_net; 96862306a36Sopenharmony_ci struct mpls_route *rt, *old; 96962306a36Sopenharmony_ci int err = -EINVAL; 97062306a36Sopenharmony_ci u8 max_via_alen; 97162306a36Sopenharmony_ci unsigned index; 97262306a36Sopenharmony_ci u8 max_labels; 97362306a36Sopenharmony_ci u8 nhs; 97462306a36Sopenharmony_ci 97562306a36Sopenharmony_ci index = cfg->rc_label; 97662306a36Sopenharmony_ci 97762306a36Sopenharmony_ci /* If a label was not specified during insert pick one */ 97862306a36Sopenharmony_ci if ((index == LABEL_NOT_SPECIFIED) && 97962306a36Sopenharmony_ci (cfg->rc_nlflags & NLM_F_CREATE)) { 98062306a36Sopenharmony_ci index = find_free_label(net); 98162306a36Sopenharmony_ci } 98262306a36Sopenharmony_ci 98362306a36Sopenharmony_ci if (!mpls_label_ok(net, &index, extack)) 98462306a36Sopenharmony_ci goto errout; 98562306a36Sopenharmony_ci 98662306a36Sopenharmony_ci /* Append makes no sense with mpls */ 98762306a36Sopenharmony_ci err = -EOPNOTSUPP; 98862306a36Sopenharmony_ci if (cfg->rc_nlflags & NLM_F_APPEND) { 98962306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "MPLS does not support route append"); 99062306a36Sopenharmony_ci goto errout; 99162306a36Sopenharmony_ci } 99262306a36Sopenharmony_ci 99362306a36Sopenharmony_ci err = -EEXIST; 99462306a36Sopenharmony_ci platform_label = rtnl_dereference(net->mpls.platform_label); 99562306a36Sopenharmony_ci old = rtnl_dereference(platform_label[index]); 99662306a36Sopenharmony_ci if ((cfg->rc_nlflags & NLM_F_EXCL) && old) 99762306a36Sopenharmony_ci goto errout; 99862306a36Sopenharmony_ci 99962306a36Sopenharmony_ci err = -EEXIST; 100062306a36Sopenharmony_ci if (!(cfg->rc_nlflags & NLM_F_REPLACE) && old) 100162306a36Sopenharmony_ci goto errout; 100262306a36Sopenharmony_ci 100362306a36Sopenharmony_ci err = -ENOENT; 100462306a36Sopenharmony_ci if (!(cfg->rc_nlflags & NLM_F_CREATE) && !old) 100562306a36Sopenharmony_ci goto errout; 100662306a36Sopenharmony_ci 100762306a36Sopenharmony_ci err = -EINVAL; 100862306a36Sopenharmony_ci if (cfg->rc_mp) { 100962306a36Sopenharmony_ci nhs = mpls_count_nexthops(cfg->rc_mp, cfg->rc_mp_len, 101062306a36Sopenharmony_ci cfg->rc_via_alen, &max_via_alen, 101162306a36Sopenharmony_ci &max_labels); 101262306a36Sopenharmony_ci } else { 101362306a36Sopenharmony_ci max_via_alen = cfg->rc_via_alen; 101462306a36Sopenharmony_ci max_labels = cfg->rc_output_labels; 101562306a36Sopenharmony_ci nhs = 1; 101662306a36Sopenharmony_ci } 101762306a36Sopenharmony_ci 101862306a36Sopenharmony_ci if (nhs == 0) { 101962306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Route does not contain a nexthop"); 102062306a36Sopenharmony_ci goto errout; 102162306a36Sopenharmony_ci } 102262306a36Sopenharmony_ci 102362306a36Sopenharmony_ci rt = mpls_rt_alloc(nhs, max_via_alen, max_labels); 102462306a36Sopenharmony_ci if (IS_ERR(rt)) { 102562306a36Sopenharmony_ci err = PTR_ERR(rt); 102662306a36Sopenharmony_ci goto errout; 102762306a36Sopenharmony_ci } 102862306a36Sopenharmony_ci 102962306a36Sopenharmony_ci rt->rt_protocol = cfg->rc_protocol; 103062306a36Sopenharmony_ci rt->rt_payload_type = cfg->rc_payload_type; 103162306a36Sopenharmony_ci rt->rt_ttl_propagate = cfg->rc_ttl_propagate; 103262306a36Sopenharmony_ci 103362306a36Sopenharmony_ci if (cfg->rc_mp) 103462306a36Sopenharmony_ci err = mpls_nh_build_multi(cfg, rt, max_labels, extack); 103562306a36Sopenharmony_ci else 103662306a36Sopenharmony_ci err = mpls_nh_build_from_cfg(cfg, rt); 103762306a36Sopenharmony_ci if (err) 103862306a36Sopenharmony_ci goto freert; 103962306a36Sopenharmony_ci 104062306a36Sopenharmony_ci mpls_route_update(net, index, rt, &cfg->rc_nlinfo); 104162306a36Sopenharmony_ci 104262306a36Sopenharmony_ci return 0; 104362306a36Sopenharmony_ci 104462306a36Sopenharmony_cifreert: 104562306a36Sopenharmony_ci mpls_rt_free(rt); 104662306a36Sopenharmony_cierrout: 104762306a36Sopenharmony_ci return err; 104862306a36Sopenharmony_ci} 104962306a36Sopenharmony_ci 105062306a36Sopenharmony_cistatic int mpls_route_del(struct mpls_route_config *cfg, 105162306a36Sopenharmony_ci struct netlink_ext_ack *extack) 105262306a36Sopenharmony_ci{ 105362306a36Sopenharmony_ci struct net *net = cfg->rc_nlinfo.nl_net; 105462306a36Sopenharmony_ci unsigned index; 105562306a36Sopenharmony_ci int err = -EINVAL; 105662306a36Sopenharmony_ci 105762306a36Sopenharmony_ci index = cfg->rc_label; 105862306a36Sopenharmony_ci 105962306a36Sopenharmony_ci if (!mpls_label_ok(net, &index, extack)) 106062306a36Sopenharmony_ci goto errout; 106162306a36Sopenharmony_ci 106262306a36Sopenharmony_ci mpls_route_update(net, index, NULL, &cfg->rc_nlinfo); 106362306a36Sopenharmony_ci 106462306a36Sopenharmony_ci err = 0; 106562306a36Sopenharmony_cierrout: 106662306a36Sopenharmony_ci return err; 106762306a36Sopenharmony_ci} 106862306a36Sopenharmony_ci 106962306a36Sopenharmony_cistatic void mpls_get_stats(struct mpls_dev *mdev, 107062306a36Sopenharmony_ci struct mpls_link_stats *stats) 107162306a36Sopenharmony_ci{ 107262306a36Sopenharmony_ci struct mpls_pcpu_stats *p; 107362306a36Sopenharmony_ci int i; 107462306a36Sopenharmony_ci 107562306a36Sopenharmony_ci memset(stats, 0, sizeof(*stats)); 107662306a36Sopenharmony_ci 107762306a36Sopenharmony_ci for_each_possible_cpu(i) { 107862306a36Sopenharmony_ci struct mpls_link_stats local; 107962306a36Sopenharmony_ci unsigned int start; 108062306a36Sopenharmony_ci 108162306a36Sopenharmony_ci p = per_cpu_ptr(mdev->stats, i); 108262306a36Sopenharmony_ci do { 108362306a36Sopenharmony_ci start = u64_stats_fetch_begin(&p->syncp); 108462306a36Sopenharmony_ci local = p->stats; 108562306a36Sopenharmony_ci } while (u64_stats_fetch_retry(&p->syncp, start)); 108662306a36Sopenharmony_ci 108762306a36Sopenharmony_ci stats->rx_packets += local.rx_packets; 108862306a36Sopenharmony_ci stats->rx_bytes += local.rx_bytes; 108962306a36Sopenharmony_ci stats->tx_packets += local.tx_packets; 109062306a36Sopenharmony_ci stats->tx_bytes += local.tx_bytes; 109162306a36Sopenharmony_ci stats->rx_errors += local.rx_errors; 109262306a36Sopenharmony_ci stats->tx_errors += local.tx_errors; 109362306a36Sopenharmony_ci stats->rx_dropped += local.rx_dropped; 109462306a36Sopenharmony_ci stats->tx_dropped += local.tx_dropped; 109562306a36Sopenharmony_ci stats->rx_noroute += local.rx_noroute; 109662306a36Sopenharmony_ci } 109762306a36Sopenharmony_ci} 109862306a36Sopenharmony_ci 109962306a36Sopenharmony_cistatic int mpls_fill_stats_af(struct sk_buff *skb, 110062306a36Sopenharmony_ci const struct net_device *dev) 110162306a36Sopenharmony_ci{ 110262306a36Sopenharmony_ci struct mpls_link_stats *stats; 110362306a36Sopenharmony_ci struct mpls_dev *mdev; 110462306a36Sopenharmony_ci struct nlattr *nla; 110562306a36Sopenharmony_ci 110662306a36Sopenharmony_ci mdev = mpls_dev_get(dev); 110762306a36Sopenharmony_ci if (!mdev) 110862306a36Sopenharmony_ci return -ENODATA; 110962306a36Sopenharmony_ci 111062306a36Sopenharmony_ci nla = nla_reserve_64bit(skb, MPLS_STATS_LINK, 111162306a36Sopenharmony_ci sizeof(struct mpls_link_stats), 111262306a36Sopenharmony_ci MPLS_STATS_UNSPEC); 111362306a36Sopenharmony_ci if (!nla) 111462306a36Sopenharmony_ci return -EMSGSIZE; 111562306a36Sopenharmony_ci 111662306a36Sopenharmony_ci stats = nla_data(nla); 111762306a36Sopenharmony_ci mpls_get_stats(mdev, stats); 111862306a36Sopenharmony_ci 111962306a36Sopenharmony_ci return 0; 112062306a36Sopenharmony_ci} 112162306a36Sopenharmony_ci 112262306a36Sopenharmony_cistatic size_t mpls_get_stats_af_size(const struct net_device *dev) 112362306a36Sopenharmony_ci{ 112462306a36Sopenharmony_ci struct mpls_dev *mdev; 112562306a36Sopenharmony_ci 112662306a36Sopenharmony_ci mdev = mpls_dev_get(dev); 112762306a36Sopenharmony_ci if (!mdev) 112862306a36Sopenharmony_ci return 0; 112962306a36Sopenharmony_ci 113062306a36Sopenharmony_ci return nla_total_size_64bit(sizeof(struct mpls_link_stats)); 113162306a36Sopenharmony_ci} 113262306a36Sopenharmony_ci 113362306a36Sopenharmony_cistatic int mpls_netconf_fill_devconf(struct sk_buff *skb, struct mpls_dev *mdev, 113462306a36Sopenharmony_ci u32 portid, u32 seq, int event, 113562306a36Sopenharmony_ci unsigned int flags, int type) 113662306a36Sopenharmony_ci{ 113762306a36Sopenharmony_ci struct nlmsghdr *nlh; 113862306a36Sopenharmony_ci struct netconfmsg *ncm; 113962306a36Sopenharmony_ci bool all = false; 114062306a36Sopenharmony_ci 114162306a36Sopenharmony_ci nlh = nlmsg_put(skb, portid, seq, event, sizeof(struct netconfmsg), 114262306a36Sopenharmony_ci flags); 114362306a36Sopenharmony_ci if (!nlh) 114462306a36Sopenharmony_ci return -EMSGSIZE; 114562306a36Sopenharmony_ci 114662306a36Sopenharmony_ci if (type == NETCONFA_ALL) 114762306a36Sopenharmony_ci all = true; 114862306a36Sopenharmony_ci 114962306a36Sopenharmony_ci ncm = nlmsg_data(nlh); 115062306a36Sopenharmony_ci ncm->ncm_family = AF_MPLS; 115162306a36Sopenharmony_ci 115262306a36Sopenharmony_ci if (nla_put_s32(skb, NETCONFA_IFINDEX, mdev->dev->ifindex) < 0) 115362306a36Sopenharmony_ci goto nla_put_failure; 115462306a36Sopenharmony_ci 115562306a36Sopenharmony_ci if ((all || type == NETCONFA_INPUT) && 115662306a36Sopenharmony_ci nla_put_s32(skb, NETCONFA_INPUT, 115762306a36Sopenharmony_ci mdev->input_enabled) < 0) 115862306a36Sopenharmony_ci goto nla_put_failure; 115962306a36Sopenharmony_ci 116062306a36Sopenharmony_ci nlmsg_end(skb, nlh); 116162306a36Sopenharmony_ci return 0; 116262306a36Sopenharmony_ci 116362306a36Sopenharmony_cinla_put_failure: 116462306a36Sopenharmony_ci nlmsg_cancel(skb, nlh); 116562306a36Sopenharmony_ci return -EMSGSIZE; 116662306a36Sopenharmony_ci} 116762306a36Sopenharmony_ci 116862306a36Sopenharmony_cistatic int mpls_netconf_msgsize_devconf(int type) 116962306a36Sopenharmony_ci{ 117062306a36Sopenharmony_ci int size = NLMSG_ALIGN(sizeof(struct netconfmsg)) 117162306a36Sopenharmony_ci + nla_total_size(4); /* NETCONFA_IFINDEX */ 117262306a36Sopenharmony_ci bool all = false; 117362306a36Sopenharmony_ci 117462306a36Sopenharmony_ci if (type == NETCONFA_ALL) 117562306a36Sopenharmony_ci all = true; 117662306a36Sopenharmony_ci 117762306a36Sopenharmony_ci if (all || type == NETCONFA_INPUT) 117862306a36Sopenharmony_ci size += nla_total_size(4); 117962306a36Sopenharmony_ci 118062306a36Sopenharmony_ci return size; 118162306a36Sopenharmony_ci} 118262306a36Sopenharmony_ci 118362306a36Sopenharmony_cistatic void mpls_netconf_notify_devconf(struct net *net, int event, 118462306a36Sopenharmony_ci int type, struct mpls_dev *mdev) 118562306a36Sopenharmony_ci{ 118662306a36Sopenharmony_ci struct sk_buff *skb; 118762306a36Sopenharmony_ci int err = -ENOBUFS; 118862306a36Sopenharmony_ci 118962306a36Sopenharmony_ci skb = nlmsg_new(mpls_netconf_msgsize_devconf(type), GFP_KERNEL); 119062306a36Sopenharmony_ci if (!skb) 119162306a36Sopenharmony_ci goto errout; 119262306a36Sopenharmony_ci 119362306a36Sopenharmony_ci err = mpls_netconf_fill_devconf(skb, mdev, 0, 0, event, 0, type); 119462306a36Sopenharmony_ci if (err < 0) { 119562306a36Sopenharmony_ci /* -EMSGSIZE implies BUG in mpls_netconf_msgsize_devconf() */ 119662306a36Sopenharmony_ci WARN_ON(err == -EMSGSIZE); 119762306a36Sopenharmony_ci kfree_skb(skb); 119862306a36Sopenharmony_ci goto errout; 119962306a36Sopenharmony_ci } 120062306a36Sopenharmony_ci 120162306a36Sopenharmony_ci rtnl_notify(skb, net, 0, RTNLGRP_MPLS_NETCONF, NULL, GFP_KERNEL); 120262306a36Sopenharmony_ci return; 120362306a36Sopenharmony_cierrout: 120462306a36Sopenharmony_ci if (err < 0) 120562306a36Sopenharmony_ci rtnl_set_sk_err(net, RTNLGRP_MPLS_NETCONF, err); 120662306a36Sopenharmony_ci} 120762306a36Sopenharmony_ci 120862306a36Sopenharmony_cistatic const struct nla_policy devconf_mpls_policy[NETCONFA_MAX + 1] = { 120962306a36Sopenharmony_ci [NETCONFA_IFINDEX] = { .len = sizeof(int) }, 121062306a36Sopenharmony_ci}; 121162306a36Sopenharmony_ci 121262306a36Sopenharmony_cistatic int mpls_netconf_valid_get_req(struct sk_buff *skb, 121362306a36Sopenharmony_ci const struct nlmsghdr *nlh, 121462306a36Sopenharmony_ci struct nlattr **tb, 121562306a36Sopenharmony_ci struct netlink_ext_ack *extack) 121662306a36Sopenharmony_ci{ 121762306a36Sopenharmony_ci int i, err; 121862306a36Sopenharmony_ci 121962306a36Sopenharmony_ci if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(struct netconfmsg))) { 122062306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, 122162306a36Sopenharmony_ci "Invalid header for netconf get request"); 122262306a36Sopenharmony_ci return -EINVAL; 122362306a36Sopenharmony_ci } 122462306a36Sopenharmony_ci 122562306a36Sopenharmony_ci if (!netlink_strict_get_check(skb)) 122662306a36Sopenharmony_ci return nlmsg_parse_deprecated(nlh, sizeof(struct netconfmsg), 122762306a36Sopenharmony_ci tb, NETCONFA_MAX, 122862306a36Sopenharmony_ci devconf_mpls_policy, extack); 122962306a36Sopenharmony_ci 123062306a36Sopenharmony_ci err = nlmsg_parse_deprecated_strict(nlh, sizeof(struct netconfmsg), 123162306a36Sopenharmony_ci tb, NETCONFA_MAX, 123262306a36Sopenharmony_ci devconf_mpls_policy, extack); 123362306a36Sopenharmony_ci if (err) 123462306a36Sopenharmony_ci return err; 123562306a36Sopenharmony_ci 123662306a36Sopenharmony_ci for (i = 0; i <= NETCONFA_MAX; i++) { 123762306a36Sopenharmony_ci if (!tb[i]) 123862306a36Sopenharmony_ci continue; 123962306a36Sopenharmony_ci 124062306a36Sopenharmony_ci switch (i) { 124162306a36Sopenharmony_ci case NETCONFA_IFINDEX: 124262306a36Sopenharmony_ci break; 124362306a36Sopenharmony_ci default: 124462306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Unsupported attribute in netconf get request"); 124562306a36Sopenharmony_ci return -EINVAL; 124662306a36Sopenharmony_ci } 124762306a36Sopenharmony_ci } 124862306a36Sopenharmony_ci 124962306a36Sopenharmony_ci return 0; 125062306a36Sopenharmony_ci} 125162306a36Sopenharmony_ci 125262306a36Sopenharmony_cistatic int mpls_netconf_get_devconf(struct sk_buff *in_skb, 125362306a36Sopenharmony_ci struct nlmsghdr *nlh, 125462306a36Sopenharmony_ci struct netlink_ext_ack *extack) 125562306a36Sopenharmony_ci{ 125662306a36Sopenharmony_ci struct net *net = sock_net(in_skb->sk); 125762306a36Sopenharmony_ci struct nlattr *tb[NETCONFA_MAX + 1]; 125862306a36Sopenharmony_ci struct net_device *dev; 125962306a36Sopenharmony_ci struct mpls_dev *mdev; 126062306a36Sopenharmony_ci struct sk_buff *skb; 126162306a36Sopenharmony_ci int ifindex; 126262306a36Sopenharmony_ci int err; 126362306a36Sopenharmony_ci 126462306a36Sopenharmony_ci err = mpls_netconf_valid_get_req(in_skb, nlh, tb, extack); 126562306a36Sopenharmony_ci if (err < 0) 126662306a36Sopenharmony_ci goto errout; 126762306a36Sopenharmony_ci 126862306a36Sopenharmony_ci err = -EINVAL; 126962306a36Sopenharmony_ci if (!tb[NETCONFA_IFINDEX]) 127062306a36Sopenharmony_ci goto errout; 127162306a36Sopenharmony_ci 127262306a36Sopenharmony_ci ifindex = nla_get_s32(tb[NETCONFA_IFINDEX]); 127362306a36Sopenharmony_ci dev = __dev_get_by_index(net, ifindex); 127462306a36Sopenharmony_ci if (!dev) 127562306a36Sopenharmony_ci goto errout; 127662306a36Sopenharmony_ci 127762306a36Sopenharmony_ci mdev = mpls_dev_get(dev); 127862306a36Sopenharmony_ci if (!mdev) 127962306a36Sopenharmony_ci goto errout; 128062306a36Sopenharmony_ci 128162306a36Sopenharmony_ci err = -ENOBUFS; 128262306a36Sopenharmony_ci skb = nlmsg_new(mpls_netconf_msgsize_devconf(NETCONFA_ALL), GFP_KERNEL); 128362306a36Sopenharmony_ci if (!skb) 128462306a36Sopenharmony_ci goto errout; 128562306a36Sopenharmony_ci 128662306a36Sopenharmony_ci err = mpls_netconf_fill_devconf(skb, mdev, 128762306a36Sopenharmony_ci NETLINK_CB(in_skb).portid, 128862306a36Sopenharmony_ci nlh->nlmsg_seq, RTM_NEWNETCONF, 0, 128962306a36Sopenharmony_ci NETCONFA_ALL); 129062306a36Sopenharmony_ci if (err < 0) { 129162306a36Sopenharmony_ci /* -EMSGSIZE implies BUG in mpls_netconf_msgsize_devconf() */ 129262306a36Sopenharmony_ci WARN_ON(err == -EMSGSIZE); 129362306a36Sopenharmony_ci kfree_skb(skb); 129462306a36Sopenharmony_ci goto errout; 129562306a36Sopenharmony_ci } 129662306a36Sopenharmony_ci err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid); 129762306a36Sopenharmony_cierrout: 129862306a36Sopenharmony_ci return err; 129962306a36Sopenharmony_ci} 130062306a36Sopenharmony_ci 130162306a36Sopenharmony_cistatic int mpls_netconf_dump_devconf(struct sk_buff *skb, 130262306a36Sopenharmony_ci struct netlink_callback *cb) 130362306a36Sopenharmony_ci{ 130462306a36Sopenharmony_ci const struct nlmsghdr *nlh = cb->nlh; 130562306a36Sopenharmony_ci struct net *net = sock_net(skb->sk); 130662306a36Sopenharmony_ci struct hlist_head *head; 130762306a36Sopenharmony_ci struct net_device *dev; 130862306a36Sopenharmony_ci struct mpls_dev *mdev; 130962306a36Sopenharmony_ci int idx, s_idx; 131062306a36Sopenharmony_ci int h, s_h; 131162306a36Sopenharmony_ci 131262306a36Sopenharmony_ci if (cb->strict_check) { 131362306a36Sopenharmony_ci struct netlink_ext_ack *extack = cb->extack; 131462306a36Sopenharmony_ci struct netconfmsg *ncm; 131562306a36Sopenharmony_ci 131662306a36Sopenharmony_ci if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*ncm))) { 131762306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Invalid header for netconf dump request"); 131862306a36Sopenharmony_ci return -EINVAL; 131962306a36Sopenharmony_ci } 132062306a36Sopenharmony_ci 132162306a36Sopenharmony_ci if (nlmsg_attrlen(nlh, sizeof(*ncm))) { 132262306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Invalid data after header in netconf dump request"); 132362306a36Sopenharmony_ci return -EINVAL; 132462306a36Sopenharmony_ci } 132562306a36Sopenharmony_ci } 132662306a36Sopenharmony_ci 132762306a36Sopenharmony_ci s_h = cb->args[0]; 132862306a36Sopenharmony_ci s_idx = idx = cb->args[1]; 132962306a36Sopenharmony_ci 133062306a36Sopenharmony_ci for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) { 133162306a36Sopenharmony_ci idx = 0; 133262306a36Sopenharmony_ci head = &net->dev_index_head[h]; 133362306a36Sopenharmony_ci rcu_read_lock(); 133462306a36Sopenharmony_ci cb->seq = net->dev_base_seq; 133562306a36Sopenharmony_ci hlist_for_each_entry_rcu(dev, head, index_hlist) { 133662306a36Sopenharmony_ci if (idx < s_idx) 133762306a36Sopenharmony_ci goto cont; 133862306a36Sopenharmony_ci mdev = mpls_dev_get(dev); 133962306a36Sopenharmony_ci if (!mdev) 134062306a36Sopenharmony_ci goto cont; 134162306a36Sopenharmony_ci if (mpls_netconf_fill_devconf(skb, mdev, 134262306a36Sopenharmony_ci NETLINK_CB(cb->skb).portid, 134362306a36Sopenharmony_ci nlh->nlmsg_seq, 134462306a36Sopenharmony_ci RTM_NEWNETCONF, 134562306a36Sopenharmony_ci NLM_F_MULTI, 134662306a36Sopenharmony_ci NETCONFA_ALL) < 0) { 134762306a36Sopenharmony_ci rcu_read_unlock(); 134862306a36Sopenharmony_ci goto done; 134962306a36Sopenharmony_ci } 135062306a36Sopenharmony_ci nl_dump_check_consistent(cb, nlmsg_hdr(skb)); 135162306a36Sopenharmony_cicont: 135262306a36Sopenharmony_ci idx++; 135362306a36Sopenharmony_ci } 135462306a36Sopenharmony_ci rcu_read_unlock(); 135562306a36Sopenharmony_ci } 135662306a36Sopenharmony_cidone: 135762306a36Sopenharmony_ci cb->args[0] = h; 135862306a36Sopenharmony_ci cb->args[1] = idx; 135962306a36Sopenharmony_ci 136062306a36Sopenharmony_ci return skb->len; 136162306a36Sopenharmony_ci} 136262306a36Sopenharmony_ci 136362306a36Sopenharmony_ci#define MPLS_PERDEV_SYSCTL_OFFSET(field) \ 136462306a36Sopenharmony_ci (&((struct mpls_dev *)0)->field) 136562306a36Sopenharmony_ci 136662306a36Sopenharmony_cistatic int mpls_conf_proc(struct ctl_table *ctl, int write, 136762306a36Sopenharmony_ci void *buffer, size_t *lenp, loff_t *ppos) 136862306a36Sopenharmony_ci{ 136962306a36Sopenharmony_ci int oval = *(int *)ctl->data; 137062306a36Sopenharmony_ci int ret = proc_dointvec(ctl, write, buffer, lenp, ppos); 137162306a36Sopenharmony_ci 137262306a36Sopenharmony_ci if (write) { 137362306a36Sopenharmony_ci struct mpls_dev *mdev = ctl->extra1; 137462306a36Sopenharmony_ci int i = (int *)ctl->data - (int *)mdev; 137562306a36Sopenharmony_ci struct net *net = ctl->extra2; 137662306a36Sopenharmony_ci int val = *(int *)ctl->data; 137762306a36Sopenharmony_ci 137862306a36Sopenharmony_ci if (i == offsetof(struct mpls_dev, input_enabled) && 137962306a36Sopenharmony_ci val != oval) { 138062306a36Sopenharmony_ci mpls_netconf_notify_devconf(net, RTM_NEWNETCONF, 138162306a36Sopenharmony_ci NETCONFA_INPUT, mdev); 138262306a36Sopenharmony_ci } 138362306a36Sopenharmony_ci } 138462306a36Sopenharmony_ci 138562306a36Sopenharmony_ci return ret; 138662306a36Sopenharmony_ci} 138762306a36Sopenharmony_ci 138862306a36Sopenharmony_cistatic const struct ctl_table mpls_dev_table[] = { 138962306a36Sopenharmony_ci { 139062306a36Sopenharmony_ci .procname = "input", 139162306a36Sopenharmony_ci .maxlen = sizeof(int), 139262306a36Sopenharmony_ci .mode = 0644, 139362306a36Sopenharmony_ci .proc_handler = mpls_conf_proc, 139462306a36Sopenharmony_ci .data = MPLS_PERDEV_SYSCTL_OFFSET(input_enabled), 139562306a36Sopenharmony_ci }, 139662306a36Sopenharmony_ci { } 139762306a36Sopenharmony_ci}; 139862306a36Sopenharmony_ci 139962306a36Sopenharmony_cistatic int mpls_dev_sysctl_register(struct net_device *dev, 140062306a36Sopenharmony_ci struct mpls_dev *mdev) 140162306a36Sopenharmony_ci{ 140262306a36Sopenharmony_ci char path[sizeof("net/mpls/conf/") + IFNAMSIZ]; 140362306a36Sopenharmony_ci struct net *net = dev_net(dev); 140462306a36Sopenharmony_ci struct ctl_table *table; 140562306a36Sopenharmony_ci int i; 140662306a36Sopenharmony_ci 140762306a36Sopenharmony_ci table = kmemdup(&mpls_dev_table, sizeof(mpls_dev_table), GFP_KERNEL); 140862306a36Sopenharmony_ci if (!table) 140962306a36Sopenharmony_ci goto out; 141062306a36Sopenharmony_ci 141162306a36Sopenharmony_ci /* Table data contains only offsets relative to the base of 141262306a36Sopenharmony_ci * the mdev at this point, so make them absolute. 141362306a36Sopenharmony_ci */ 141462306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(mpls_dev_table); i++) { 141562306a36Sopenharmony_ci table[i].data = (char *)mdev + (uintptr_t)table[i].data; 141662306a36Sopenharmony_ci table[i].extra1 = mdev; 141762306a36Sopenharmony_ci table[i].extra2 = net; 141862306a36Sopenharmony_ci } 141962306a36Sopenharmony_ci 142062306a36Sopenharmony_ci snprintf(path, sizeof(path), "net/mpls/conf/%s", dev->name); 142162306a36Sopenharmony_ci 142262306a36Sopenharmony_ci mdev->sysctl = register_net_sysctl_sz(net, path, table, 142362306a36Sopenharmony_ci ARRAY_SIZE(mpls_dev_table)); 142462306a36Sopenharmony_ci if (!mdev->sysctl) 142562306a36Sopenharmony_ci goto free; 142662306a36Sopenharmony_ci 142762306a36Sopenharmony_ci mpls_netconf_notify_devconf(net, RTM_NEWNETCONF, NETCONFA_ALL, mdev); 142862306a36Sopenharmony_ci return 0; 142962306a36Sopenharmony_ci 143062306a36Sopenharmony_cifree: 143162306a36Sopenharmony_ci kfree(table); 143262306a36Sopenharmony_ciout: 143362306a36Sopenharmony_ci mdev->sysctl = NULL; 143462306a36Sopenharmony_ci return -ENOBUFS; 143562306a36Sopenharmony_ci} 143662306a36Sopenharmony_ci 143762306a36Sopenharmony_cistatic void mpls_dev_sysctl_unregister(struct net_device *dev, 143862306a36Sopenharmony_ci struct mpls_dev *mdev) 143962306a36Sopenharmony_ci{ 144062306a36Sopenharmony_ci struct net *net = dev_net(dev); 144162306a36Sopenharmony_ci struct ctl_table *table; 144262306a36Sopenharmony_ci 144362306a36Sopenharmony_ci if (!mdev->sysctl) 144462306a36Sopenharmony_ci return; 144562306a36Sopenharmony_ci 144662306a36Sopenharmony_ci table = mdev->sysctl->ctl_table_arg; 144762306a36Sopenharmony_ci unregister_net_sysctl_table(mdev->sysctl); 144862306a36Sopenharmony_ci kfree(table); 144962306a36Sopenharmony_ci 145062306a36Sopenharmony_ci mpls_netconf_notify_devconf(net, RTM_DELNETCONF, 0, mdev); 145162306a36Sopenharmony_ci} 145262306a36Sopenharmony_ci 145362306a36Sopenharmony_cistatic struct mpls_dev *mpls_add_dev(struct net_device *dev) 145462306a36Sopenharmony_ci{ 145562306a36Sopenharmony_ci struct mpls_dev *mdev; 145662306a36Sopenharmony_ci int err = -ENOMEM; 145762306a36Sopenharmony_ci int i; 145862306a36Sopenharmony_ci 145962306a36Sopenharmony_ci ASSERT_RTNL(); 146062306a36Sopenharmony_ci 146162306a36Sopenharmony_ci mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); 146262306a36Sopenharmony_ci if (!mdev) 146362306a36Sopenharmony_ci return ERR_PTR(err); 146462306a36Sopenharmony_ci 146562306a36Sopenharmony_ci mdev->stats = alloc_percpu(struct mpls_pcpu_stats); 146662306a36Sopenharmony_ci if (!mdev->stats) 146762306a36Sopenharmony_ci goto free; 146862306a36Sopenharmony_ci 146962306a36Sopenharmony_ci for_each_possible_cpu(i) { 147062306a36Sopenharmony_ci struct mpls_pcpu_stats *mpls_stats; 147162306a36Sopenharmony_ci 147262306a36Sopenharmony_ci mpls_stats = per_cpu_ptr(mdev->stats, i); 147362306a36Sopenharmony_ci u64_stats_init(&mpls_stats->syncp); 147462306a36Sopenharmony_ci } 147562306a36Sopenharmony_ci 147662306a36Sopenharmony_ci mdev->dev = dev; 147762306a36Sopenharmony_ci 147862306a36Sopenharmony_ci err = mpls_dev_sysctl_register(dev, mdev); 147962306a36Sopenharmony_ci if (err) 148062306a36Sopenharmony_ci goto free; 148162306a36Sopenharmony_ci 148262306a36Sopenharmony_ci rcu_assign_pointer(dev->mpls_ptr, mdev); 148362306a36Sopenharmony_ci 148462306a36Sopenharmony_ci return mdev; 148562306a36Sopenharmony_ci 148662306a36Sopenharmony_cifree: 148762306a36Sopenharmony_ci free_percpu(mdev->stats); 148862306a36Sopenharmony_ci kfree(mdev); 148962306a36Sopenharmony_ci return ERR_PTR(err); 149062306a36Sopenharmony_ci} 149162306a36Sopenharmony_ci 149262306a36Sopenharmony_cistatic void mpls_dev_destroy_rcu(struct rcu_head *head) 149362306a36Sopenharmony_ci{ 149462306a36Sopenharmony_ci struct mpls_dev *mdev = container_of(head, struct mpls_dev, rcu); 149562306a36Sopenharmony_ci 149662306a36Sopenharmony_ci free_percpu(mdev->stats); 149762306a36Sopenharmony_ci kfree(mdev); 149862306a36Sopenharmony_ci} 149962306a36Sopenharmony_ci 150062306a36Sopenharmony_cistatic int mpls_ifdown(struct net_device *dev, int event) 150162306a36Sopenharmony_ci{ 150262306a36Sopenharmony_ci struct mpls_route __rcu **platform_label; 150362306a36Sopenharmony_ci struct net *net = dev_net(dev); 150462306a36Sopenharmony_ci unsigned index; 150562306a36Sopenharmony_ci 150662306a36Sopenharmony_ci platform_label = rtnl_dereference(net->mpls.platform_label); 150762306a36Sopenharmony_ci for (index = 0; index < net->mpls.platform_labels; index++) { 150862306a36Sopenharmony_ci struct mpls_route *rt = rtnl_dereference(platform_label[index]); 150962306a36Sopenharmony_ci bool nh_del = false; 151062306a36Sopenharmony_ci u8 alive = 0; 151162306a36Sopenharmony_ci 151262306a36Sopenharmony_ci if (!rt) 151362306a36Sopenharmony_ci continue; 151462306a36Sopenharmony_ci 151562306a36Sopenharmony_ci if (event == NETDEV_UNREGISTER) { 151662306a36Sopenharmony_ci u8 deleted = 0; 151762306a36Sopenharmony_ci 151862306a36Sopenharmony_ci for_nexthops(rt) { 151962306a36Sopenharmony_ci if (!nh->nh_dev || nh->nh_dev == dev) 152062306a36Sopenharmony_ci deleted++; 152162306a36Sopenharmony_ci if (nh->nh_dev == dev) 152262306a36Sopenharmony_ci nh_del = true; 152362306a36Sopenharmony_ci } endfor_nexthops(rt); 152462306a36Sopenharmony_ci 152562306a36Sopenharmony_ci /* if there are no more nexthops, delete the route */ 152662306a36Sopenharmony_ci if (deleted == rt->rt_nhn) { 152762306a36Sopenharmony_ci mpls_route_update(net, index, NULL, NULL); 152862306a36Sopenharmony_ci continue; 152962306a36Sopenharmony_ci } 153062306a36Sopenharmony_ci 153162306a36Sopenharmony_ci if (nh_del) { 153262306a36Sopenharmony_ci size_t size = sizeof(*rt) + rt->rt_nhn * 153362306a36Sopenharmony_ci rt->rt_nh_size; 153462306a36Sopenharmony_ci struct mpls_route *orig = rt; 153562306a36Sopenharmony_ci 153662306a36Sopenharmony_ci rt = kmemdup(orig, size, GFP_KERNEL); 153762306a36Sopenharmony_ci if (!rt) 153862306a36Sopenharmony_ci return -ENOMEM; 153962306a36Sopenharmony_ci } 154062306a36Sopenharmony_ci } 154162306a36Sopenharmony_ci 154262306a36Sopenharmony_ci change_nexthops(rt) { 154362306a36Sopenharmony_ci unsigned int nh_flags = nh->nh_flags; 154462306a36Sopenharmony_ci 154562306a36Sopenharmony_ci if (nh->nh_dev != dev) 154662306a36Sopenharmony_ci goto next; 154762306a36Sopenharmony_ci 154862306a36Sopenharmony_ci switch (event) { 154962306a36Sopenharmony_ci case NETDEV_DOWN: 155062306a36Sopenharmony_ci case NETDEV_UNREGISTER: 155162306a36Sopenharmony_ci nh_flags |= RTNH_F_DEAD; 155262306a36Sopenharmony_ci fallthrough; 155362306a36Sopenharmony_ci case NETDEV_CHANGE: 155462306a36Sopenharmony_ci nh_flags |= RTNH_F_LINKDOWN; 155562306a36Sopenharmony_ci break; 155662306a36Sopenharmony_ci } 155762306a36Sopenharmony_ci if (event == NETDEV_UNREGISTER) 155862306a36Sopenharmony_ci nh->nh_dev = NULL; 155962306a36Sopenharmony_ci 156062306a36Sopenharmony_ci if (nh->nh_flags != nh_flags) 156162306a36Sopenharmony_ci WRITE_ONCE(nh->nh_flags, nh_flags); 156262306a36Sopenharmony_cinext: 156362306a36Sopenharmony_ci if (!(nh_flags & (RTNH_F_DEAD | RTNH_F_LINKDOWN))) 156462306a36Sopenharmony_ci alive++; 156562306a36Sopenharmony_ci } endfor_nexthops(rt); 156662306a36Sopenharmony_ci 156762306a36Sopenharmony_ci WRITE_ONCE(rt->rt_nhn_alive, alive); 156862306a36Sopenharmony_ci 156962306a36Sopenharmony_ci if (nh_del) 157062306a36Sopenharmony_ci mpls_route_update(net, index, rt, NULL); 157162306a36Sopenharmony_ci } 157262306a36Sopenharmony_ci 157362306a36Sopenharmony_ci return 0; 157462306a36Sopenharmony_ci} 157562306a36Sopenharmony_ci 157662306a36Sopenharmony_cistatic void mpls_ifup(struct net_device *dev, unsigned int flags) 157762306a36Sopenharmony_ci{ 157862306a36Sopenharmony_ci struct mpls_route __rcu **platform_label; 157962306a36Sopenharmony_ci struct net *net = dev_net(dev); 158062306a36Sopenharmony_ci unsigned index; 158162306a36Sopenharmony_ci u8 alive; 158262306a36Sopenharmony_ci 158362306a36Sopenharmony_ci platform_label = rtnl_dereference(net->mpls.platform_label); 158462306a36Sopenharmony_ci for (index = 0; index < net->mpls.platform_labels; index++) { 158562306a36Sopenharmony_ci struct mpls_route *rt = rtnl_dereference(platform_label[index]); 158662306a36Sopenharmony_ci 158762306a36Sopenharmony_ci if (!rt) 158862306a36Sopenharmony_ci continue; 158962306a36Sopenharmony_ci 159062306a36Sopenharmony_ci alive = 0; 159162306a36Sopenharmony_ci change_nexthops(rt) { 159262306a36Sopenharmony_ci unsigned int nh_flags = nh->nh_flags; 159362306a36Sopenharmony_ci 159462306a36Sopenharmony_ci if (!(nh_flags & flags)) { 159562306a36Sopenharmony_ci alive++; 159662306a36Sopenharmony_ci continue; 159762306a36Sopenharmony_ci } 159862306a36Sopenharmony_ci if (nh->nh_dev != dev) 159962306a36Sopenharmony_ci continue; 160062306a36Sopenharmony_ci alive++; 160162306a36Sopenharmony_ci nh_flags &= ~flags; 160262306a36Sopenharmony_ci WRITE_ONCE(nh->nh_flags, nh_flags); 160362306a36Sopenharmony_ci } endfor_nexthops(rt); 160462306a36Sopenharmony_ci 160562306a36Sopenharmony_ci WRITE_ONCE(rt->rt_nhn_alive, alive); 160662306a36Sopenharmony_ci } 160762306a36Sopenharmony_ci} 160862306a36Sopenharmony_ci 160962306a36Sopenharmony_cistatic int mpls_dev_notify(struct notifier_block *this, unsigned long event, 161062306a36Sopenharmony_ci void *ptr) 161162306a36Sopenharmony_ci{ 161262306a36Sopenharmony_ci struct net_device *dev = netdev_notifier_info_to_dev(ptr); 161362306a36Sopenharmony_ci struct mpls_dev *mdev; 161462306a36Sopenharmony_ci unsigned int flags; 161562306a36Sopenharmony_ci int err; 161662306a36Sopenharmony_ci 161762306a36Sopenharmony_ci if (event == NETDEV_REGISTER) { 161862306a36Sopenharmony_ci mdev = mpls_add_dev(dev); 161962306a36Sopenharmony_ci if (IS_ERR(mdev)) 162062306a36Sopenharmony_ci return notifier_from_errno(PTR_ERR(mdev)); 162162306a36Sopenharmony_ci 162262306a36Sopenharmony_ci return NOTIFY_OK; 162362306a36Sopenharmony_ci } 162462306a36Sopenharmony_ci 162562306a36Sopenharmony_ci mdev = mpls_dev_get(dev); 162662306a36Sopenharmony_ci if (!mdev) 162762306a36Sopenharmony_ci return NOTIFY_OK; 162862306a36Sopenharmony_ci 162962306a36Sopenharmony_ci switch (event) { 163062306a36Sopenharmony_ci 163162306a36Sopenharmony_ci case NETDEV_DOWN: 163262306a36Sopenharmony_ci err = mpls_ifdown(dev, event); 163362306a36Sopenharmony_ci if (err) 163462306a36Sopenharmony_ci return notifier_from_errno(err); 163562306a36Sopenharmony_ci break; 163662306a36Sopenharmony_ci case NETDEV_UP: 163762306a36Sopenharmony_ci flags = dev_get_flags(dev); 163862306a36Sopenharmony_ci if (flags & (IFF_RUNNING | IFF_LOWER_UP)) 163962306a36Sopenharmony_ci mpls_ifup(dev, RTNH_F_DEAD | RTNH_F_LINKDOWN); 164062306a36Sopenharmony_ci else 164162306a36Sopenharmony_ci mpls_ifup(dev, RTNH_F_DEAD); 164262306a36Sopenharmony_ci break; 164362306a36Sopenharmony_ci case NETDEV_CHANGE: 164462306a36Sopenharmony_ci flags = dev_get_flags(dev); 164562306a36Sopenharmony_ci if (flags & (IFF_RUNNING | IFF_LOWER_UP)) { 164662306a36Sopenharmony_ci mpls_ifup(dev, RTNH_F_DEAD | RTNH_F_LINKDOWN); 164762306a36Sopenharmony_ci } else { 164862306a36Sopenharmony_ci err = mpls_ifdown(dev, event); 164962306a36Sopenharmony_ci if (err) 165062306a36Sopenharmony_ci return notifier_from_errno(err); 165162306a36Sopenharmony_ci } 165262306a36Sopenharmony_ci break; 165362306a36Sopenharmony_ci case NETDEV_UNREGISTER: 165462306a36Sopenharmony_ci err = mpls_ifdown(dev, event); 165562306a36Sopenharmony_ci if (err) 165662306a36Sopenharmony_ci return notifier_from_errno(err); 165762306a36Sopenharmony_ci mdev = mpls_dev_get(dev); 165862306a36Sopenharmony_ci if (mdev) { 165962306a36Sopenharmony_ci mpls_dev_sysctl_unregister(dev, mdev); 166062306a36Sopenharmony_ci RCU_INIT_POINTER(dev->mpls_ptr, NULL); 166162306a36Sopenharmony_ci call_rcu(&mdev->rcu, mpls_dev_destroy_rcu); 166262306a36Sopenharmony_ci } 166362306a36Sopenharmony_ci break; 166462306a36Sopenharmony_ci case NETDEV_CHANGENAME: 166562306a36Sopenharmony_ci mdev = mpls_dev_get(dev); 166662306a36Sopenharmony_ci if (mdev) { 166762306a36Sopenharmony_ci mpls_dev_sysctl_unregister(dev, mdev); 166862306a36Sopenharmony_ci err = mpls_dev_sysctl_register(dev, mdev); 166962306a36Sopenharmony_ci if (err) 167062306a36Sopenharmony_ci return notifier_from_errno(err); 167162306a36Sopenharmony_ci } 167262306a36Sopenharmony_ci break; 167362306a36Sopenharmony_ci } 167462306a36Sopenharmony_ci return NOTIFY_OK; 167562306a36Sopenharmony_ci} 167662306a36Sopenharmony_ci 167762306a36Sopenharmony_cistatic struct notifier_block mpls_dev_notifier = { 167862306a36Sopenharmony_ci .notifier_call = mpls_dev_notify, 167962306a36Sopenharmony_ci}; 168062306a36Sopenharmony_ci 168162306a36Sopenharmony_cistatic int nla_put_via(struct sk_buff *skb, 168262306a36Sopenharmony_ci u8 table, const void *addr, int alen) 168362306a36Sopenharmony_ci{ 168462306a36Sopenharmony_ci static const int table_to_family[NEIGH_NR_TABLES + 1] = { 168562306a36Sopenharmony_ci AF_INET, AF_INET6, AF_DECnet, AF_PACKET, 168662306a36Sopenharmony_ci }; 168762306a36Sopenharmony_ci struct nlattr *nla; 168862306a36Sopenharmony_ci struct rtvia *via; 168962306a36Sopenharmony_ci int family = AF_UNSPEC; 169062306a36Sopenharmony_ci 169162306a36Sopenharmony_ci nla = nla_reserve(skb, RTA_VIA, alen + 2); 169262306a36Sopenharmony_ci if (!nla) 169362306a36Sopenharmony_ci return -EMSGSIZE; 169462306a36Sopenharmony_ci 169562306a36Sopenharmony_ci if (table <= NEIGH_NR_TABLES) 169662306a36Sopenharmony_ci family = table_to_family[table]; 169762306a36Sopenharmony_ci 169862306a36Sopenharmony_ci via = nla_data(nla); 169962306a36Sopenharmony_ci via->rtvia_family = family; 170062306a36Sopenharmony_ci memcpy(via->rtvia_addr, addr, alen); 170162306a36Sopenharmony_ci return 0; 170262306a36Sopenharmony_ci} 170362306a36Sopenharmony_ci 170462306a36Sopenharmony_ciint nla_put_labels(struct sk_buff *skb, int attrtype, 170562306a36Sopenharmony_ci u8 labels, const u32 label[]) 170662306a36Sopenharmony_ci{ 170762306a36Sopenharmony_ci struct nlattr *nla; 170862306a36Sopenharmony_ci struct mpls_shim_hdr *nla_label; 170962306a36Sopenharmony_ci bool bos; 171062306a36Sopenharmony_ci int i; 171162306a36Sopenharmony_ci nla = nla_reserve(skb, attrtype, labels*4); 171262306a36Sopenharmony_ci if (!nla) 171362306a36Sopenharmony_ci return -EMSGSIZE; 171462306a36Sopenharmony_ci 171562306a36Sopenharmony_ci nla_label = nla_data(nla); 171662306a36Sopenharmony_ci bos = true; 171762306a36Sopenharmony_ci for (i = labels - 1; i >= 0; i--) { 171862306a36Sopenharmony_ci nla_label[i] = mpls_entry_encode(label[i], 0, 0, bos); 171962306a36Sopenharmony_ci bos = false; 172062306a36Sopenharmony_ci } 172162306a36Sopenharmony_ci 172262306a36Sopenharmony_ci return 0; 172362306a36Sopenharmony_ci} 172462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nla_put_labels); 172562306a36Sopenharmony_ci 172662306a36Sopenharmony_ciint nla_get_labels(const struct nlattr *nla, u8 max_labels, u8 *labels, 172762306a36Sopenharmony_ci u32 label[], struct netlink_ext_ack *extack) 172862306a36Sopenharmony_ci{ 172962306a36Sopenharmony_ci unsigned len = nla_len(nla); 173062306a36Sopenharmony_ci struct mpls_shim_hdr *nla_label; 173162306a36Sopenharmony_ci u8 nla_labels; 173262306a36Sopenharmony_ci bool bos; 173362306a36Sopenharmony_ci int i; 173462306a36Sopenharmony_ci 173562306a36Sopenharmony_ci /* len needs to be an even multiple of 4 (the label size). Number 173662306a36Sopenharmony_ci * of labels is a u8 so check for overflow. 173762306a36Sopenharmony_ci */ 173862306a36Sopenharmony_ci if (len & 3 || len / 4 > 255) { 173962306a36Sopenharmony_ci NL_SET_ERR_MSG_ATTR(extack, nla, 174062306a36Sopenharmony_ci "Invalid length for labels attribute"); 174162306a36Sopenharmony_ci return -EINVAL; 174262306a36Sopenharmony_ci } 174362306a36Sopenharmony_ci 174462306a36Sopenharmony_ci /* Limit the number of new labels allowed */ 174562306a36Sopenharmony_ci nla_labels = len/4; 174662306a36Sopenharmony_ci if (nla_labels > max_labels) { 174762306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Too many labels"); 174862306a36Sopenharmony_ci return -EINVAL; 174962306a36Sopenharmony_ci } 175062306a36Sopenharmony_ci 175162306a36Sopenharmony_ci /* when label == NULL, caller wants number of labels */ 175262306a36Sopenharmony_ci if (!label) 175362306a36Sopenharmony_ci goto out; 175462306a36Sopenharmony_ci 175562306a36Sopenharmony_ci nla_label = nla_data(nla); 175662306a36Sopenharmony_ci bos = true; 175762306a36Sopenharmony_ci for (i = nla_labels - 1; i >= 0; i--, bos = false) { 175862306a36Sopenharmony_ci struct mpls_entry_decoded dec; 175962306a36Sopenharmony_ci dec = mpls_entry_decode(nla_label + i); 176062306a36Sopenharmony_ci 176162306a36Sopenharmony_ci /* Ensure the bottom of stack flag is properly set 176262306a36Sopenharmony_ci * and ttl and tc are both clear. 176362306a36Sopenharmony_ci */ 176462306a36Sopenharmony_ci if (dec.ttl) { 176562306a36Sopenharmony_ci NL_SET_ERR_MSG_ATTR(extack, nla, 176662306a36Sopenharmony_ci "TTL in label must be 0"); 176762306a36Sopenharmony_ci return -EINVAL; 176862306a36Sopenharmony_ci } 176962306a36Sopenharmony_ci 177062306a36Sopenharmony_ci if (dec.tc) { 177162306a36Sopenharmony_ci NL_SET_ERR_MSG_ATTR(extack, nla, 177262306a36Sopenharmony_ci "Traffic class in label must be 0"); 177362306a36Sopenharmony_ci return -EINVAL; 177462306a36Sopenharmony_ci } 177562306a36Sopenharmony_ci 177662306a36Sopenharmony_ci if (dec.bos != bos) { 177762306a36Sopenharmony_ci NL_SET_BAD_ATTR(extack, nla); 177862306a36Sopenharmony_ci if (bos) { 177962306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 178062306a36Sopenharmony_ci "BOS bit must be set in first label"); 178162306a36Sopenharmony_ci } else { 178262306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 178362306a36Sopenharmony_ci "BOS bit can only be set in first label"); 178462306a36Sopenharmony_ci } 178562306a36Sopenharmony_ci return -EINVAL; 178662306a36Sopenharmony_ci } 178762306a36Sopenharmony_ci 178862306a36Sopenharmony_ci switch (dec.label) { 178962306a36Sopenharmony_ci case MPLS_LABEL_IMPLNULL: 179062306a36Sopenharmony_ci /* RFC3032: This is a label that an LSR may 179162306a36Sopenharmony_ci * assign and distribute, but which never 179262306a36Sopenharmony_ci * actually appears in the encapsulation. 179362306a36Sopenharmony_ci */ 179462306a36Sopenharmony_ci NL_SET_ERR_MSG_ATTR(extack, nla, 179562306a36Sopenharmony_ci "Implicit NULL Label (3) can not be used in encapsulation"); 179662306a36Sopenharmony_ci return -EINVAL; 179762306a36Sopenharmony_ci } 179862306a36Sopenharmony_ci 179962306a36Sopenharmony_ci label[i] = dec.label; 180062306a36Sopenharmony_ci } 180162306a36Sopenharmony_ciout: 180262306a36Sopenharmony_ci *labels = nla_labels; 180362306a36Sopenharmony_ci return 0; 180462306a36Sopenharmony_ci} 180562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(nla_get_labels); 180662306a36Sopenharmony_ci 180762306a36Sopenharmony_cistatic int rtm_to_route_config(struct sk_buff *skb, 180862306a36Sopenharmony_ci struct nlmsghdr *nlh, 180962306a36Sopenharmony_ci struct mpls_route_config *cfg, 181062306a36Sopenharmony_ci struct netlink_ext_ack *extack) 181162306a36Sopenharmony_ci{ 181262306a36Sopenharmony_ci struct rtmsg *rtm; 181362306a36Sopenharmony_ci struct nlattr *tb[RTA_MAX+1]; 181462306a36Sopenharmony_ci int index; 181562306a36Sopenharmony_ci int err; 181662306a36Sopenharmony_ci 181762306a36Sopenharmony_ci err = nlmsg_parse_deprecated(nlh, sizeof(*rtm), tb, RTA_MAX, 181862306a36Sopenharmony_ci rtm_mpls_policy, extack); 181962306a36Sopenharmony_ci if (err < 0) 182062306a36Sopenharmony_ci goto errout; 182162306a36Sopenharmony_ci 182262306a36Sopenharmony_ci err = -EINVAL; 182362306a36Sopenharmony_ci rtm = nlmsg_data(nlh); 182462306a36Sopenharmony_ci 182562306a36Sopenharmony_ci if (rtm->rtm_family != AF_MPLS) { 182662306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Invalid address family in rtmsg"); 182762306a36Sopenharmony_ci goto errout; 182862306a36Sopenharmony_ci } 182962306a36Sopenharmony_ci if (rtm->rtm_dst_len != 20) { 183062306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "rtm_dst_len must be 20 for MPLS"); 183162306a36Sopenharmony_ci goto errout; 183262306a36Sopenharmony_ci } 183362306a36Sopenharmony_ci if (rtm->rtm_src_len != 0) { 183462306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "rtm_src_len must be 0 for MPLS"); 183562306a36Sopenharmony_ci goto errout; 183662306a36Sopenharmony_ci } 183762306a36Sopenharmony_ci if (rtm->rtm_tos != 0) { 183862306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "rtm_tos must be 0 for MPLS"); 183962306a36Sopenharmony_ci goto errout; 184062306a36Sopenharmony_ci } 184162306a36Sopenharmony_ci if (rtm->rtm_table != RT_TABLE_MAIN) { 184262306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 184362306a36Sopenharmony_ci "MPLS only supports the main route table"); 184462306a36Sopenharmony_ci goto errout; 184562306a36Sopenharmony_ci } 184662306a36Sopenharmony_ci /* Any value is acceptable for rtm_protocol */ 184762306a36Sopenharmony_ci 184862306a36Sopenharmony_ci /* As mpls uses destination specific addresses 184962306a36Sopenharmony_ci * (or source specific address in the case of multicast) 185062306a36Sopenharmony_ci * all addresses have universal scope. 185162306a36Sopenharmony_ci */ 185262306a36Sopenharmony_ci if (rtm->rtm_scope != RT_SCOPE_UNIVERSE) { 185362306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 185462306a36Sopenharmony_ci "Invalid route scope - MPLS only supports UNIVERSE"); 185562306a36Sopenharmony_ci goto errout; 185662306a36Sopenharmony_ci } 185762306a36Sopenharmony_ci if (rtm->rtm_type != RTN_UNICAST) { 185862306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, 185962306a36Sopenharmony_ci "Invalid route type - MPLS only supports UNICAST"); 186062306a36Sopenharmony_ci goto errout; 186162306a36Sopenharmony_ci } 186262306a36Sopenharmony_ci if (rtm->rtm_flags != 0) { 186362306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "rtm_flags must be 0 for MPLS"); 186462306a36Sopenharmony_ci goto errout; 186562306a36Sopenharmony_ci } 186662306a36Sopenharmony_ci 186762306a36Sopenharmony_ci cfg->rc_label = LABEL_NOT_SPECIFIED; 186862306a36Sopenharmony_ci cfg->rc_protocol = rtm->rtm_protocol; 186962306a36Sopenharmony_ci cfg->rc_via_table = MPLS_NEIGH_TABLE_UNSPEC; 187062306a36Sopenharmony_ci cfg->rc_ttl_propagate = MPLS_TTL_PROP_DEFAULT; 187162306a36Sopenharmony_ci cfg->rc_nlflags = nlh->nlmsg_flags; 187262306a36Sopenharmony_ci cfg->rc_nlinfo.portid = NETLINK_CB(skb).portid; 187362306a36Sopenharmony_ci cfg->rc_nlinfo.nlh = nlh; 187462306a36Sopenharmony_ci cfg->rc_nlinfo.nl_net = sock_net(skb->sk); 187562306a36Sopenharmony_ci 187662306a36Sopenharmony_ci for (index = 0; index <= RTA_MAX; index++) { 187762306a36Sopenharmony_ci struct nlattr *nla = tb[index]; 187862306a36Sopenharmony_ci if (!nla) 187962306a36Sopenharmony_ci continue; 188062306a36Sopenharmony_ci 188162306a36Sopenharmony_ci switch (index) { 188262306a36Sopenharmony_ci case RTA_OIF: 188362306a36Sopenharmony_ci cfg->rc_ifindex = nla_get_u32(nla); 188462306a36Sopenharmony_ci break; 188562306a36Sopenharmony_ci case RTA_NEWDST: 188662306a36Sopenharmony_ci if (nla_get_labels(nla, MAX_NEW_LABELS, 188762306a36Sopenharmony_ci &cfg->rc_output_labels, 188862306a36Sopenharmony_ci cfg->rc_output_label, extack)) 188962306a36Sopenharmony_ci goto errout; 189062306a36Sopenharmony_ci break; 189162306a36Sopenharmony_ci case RTA_DST: 189262306a36Sopenharmony_ci { 189362306a36Sopenharmony_ci u8 label_count; 189462306a36Sopenharmony_ci if (nla_get_labels(nla, 1, &label_count, 189562306a36Sopenharmony_ci &cfg->rc_label, extack)) 189662306a36Sopenharmony_ci goto errout; 189762306a36Sopenharmony_ci 189862306a36Sopenharmony_ci if (!mpls_label_ok(cfg->rc_nlinfo.nl_net, 189962306a36Sopenharmony_ci &cfg->rc_label, extack)) 190062306a36Sopenharmony_ci goto errout; 190162306a36Sopenharmony_ci break; 190262306a36Sopenharmony_ci } 190362306a36Sopenharmony_ci case RTA_GATEWAY: 190462306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "MPLS does not support RTA_GATEWAY attribute"); 190562306a36Sopenharmony_ci goto errout; 190662306a36Sopenharmony_ci case RTA_VIA: 190762306a36Sopenharmony_ci { 190862306a36Sopenharmony_ci if (nla_get_via(nla, &cfg->rc_via_alen, 190962306a36Sopenharmony_ci &cfg->rc_via_table, cfg->rc_via, 191062306a36Sopenharmony_ci extack)) 191162306a36Sopenharmony_ci goto errout; 191262306a36Sopenharmony_ci break; 191362306a36Sopenharmony_ci } 191462306a36Sopenharmony_ci case RTA_MULTIPATH: 191562306a36Sopenharmony_ci { 191662306a36Sopenharmony_ci cfg->rc_mp = nla_data(nla); 191762306a36Sopenharmony_ci cfg->rc_mp_len = nla_len(nla); 191862306a36Sopenharmony_ci break; 191962306a36Sopenharmony_ci } 192062306a36Sopenharmony_ci case RTA_TTL_PROPAGATE: 192162306a36Sopenharmony_ci { 192262306a36Sopenharmony_ci u8 ttl_propagate = nla_get_u8(nla); 192362306a36Sopenharmony_ci 192462306a36Sopenharmony_ci if (ttl_propagate > 1) { 192562306a36Sopenharmony_ci NL_SET_ERR_MSG_ATTR(extack, nla, 192662306a36Sopenharmony_ci "RTA_TTL_PROPAGATE can only be 0 or 1"); 192762306a36Sopenharmony_ci goto errout; 192862306a36Sopenharmony_ci } 192962306a36Sopenharmony_ci cfg->rc_ttl_propagate = ttl_propagate ? 193062306a36Sopenharmony_ci MPLS_TTL_PROP_ENABLED : 193162306a36Sopenharmony_ci MPLS_TTL_PROP_DISABLED; 193262306a36Sopenharmony_ci break; 193362306a36Sopenharmony_ci } 193462306a36Sopenharmony_ci default: 193562306a36Sopenharmony_ci NL_SET_ERR_MSG_ATTR(extack, nla, "Unknown attribute"); 193662306a36Sopenharmony_ci /* Unsupported attribute */ 193762306a36Sopenharmony_ci goto errout; 193862306a36Sopenharmony_ci } 193962306a36Sopenharmony_ci } 194062306a36Sopenharmony_ci 194162306a36Sopenharmony_ci err = 0; 194262306a36Sopenharmony_cierrout: 194362306a36Sopenharmony_ci return err; 194462306a36Sopenharmony_ci} 194562306a36Sopenharmony_ci 194662306a36Sopenharmony_cistatic int mpls_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh, 194762306a36Sopenharmony_ci struct netlink_ext_ack *extack) 194862306a36Sopenharmony_ci{ 194962306a36Sopenharmony_ci struct mpls_route_config *cfg; 195062306a36Sopenharmony_ci int err; 195162306a36Sopenharmony_ci 195262306a36Sopenharmony_ci cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); 195362306a36Sopenharmony_ci if (!cfg) 195462306a36Sopenharmony_ci return -ENOMEM; 195562306a36Sopenharmony_ci 195662306a36Sopenharmony_ci err = rtm_to_route_config(skb, nlh, cfg, extack); 195762306a36Sopenharmony_ci if (err < 0) 195862306a36Sopenharmony_ci goto out; 195962306a36Sopenharmony_ci 196062306a36Sopenharmony_ci err = mpls_route_del(cfg, extack); 196162306a36Sopenharmony_ciout: 196262306a36Sopenharmony_ci kfree(cfg); 196362306a36Sopenharmony_ci 196462306a36Sopenharmony_ci return err; 196562306a36Sopenharmony_ci} 196662306a36Sopenharmony_ci 196762306a36Sopenharmony_ci 196862306a36Sopenharmony_cistatic int mpls_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, 196962306a36Sopenharmony_ci struct netlink_ext_ack *extack) 197062306a36Sopenharmony_ci{ 197162306a36Sopenharmony_ci struct mpls_route_config *cfg; 197262306a36Sopenharmony_ci int err; 197362306a36Sopenharmony_ci 197462306a36Sopenharmony_ci cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); 197562306a36Sopenharmony_ci if (!cfg) 197662306a36Sopenharmony_ci return -ENOMEM; 197762306a36Sopenharmony_ci 197862306a36Sopenharmony_ci err = rtm_to_route_config(skb, nlh, cfg, extack); 197962306a36Sopenharmony_ci if (err < 0) 198062306a36Sopenharmony_ci goto out; 198162306a36Sopenharmony_ci 198262306a36Sopenharmony_ci err = mpls_route_add(cfg, extack); 198362306a36Sopenharmony_ciout: 198462306a36Sopenharmony_ci kfree(cfg); 198562306a36Sopenharmony_ci 198662306a36Sopenharmony_ci return err; 198762306a36Sopenharmony_ci} 198862306a36Sopenharmony_ci 198962306a36Sopenharmony_cistatic int mpls_dump_route(struct sk_buff *skb, u32 portid, u32 seq, int event, 199062306a36Sopenharmony_ci u32 label, struct mpls_route *rt, int flags) 199162306a36Sopenharmony_ci{ 199262306a36Sopenharmony_ci struct net_device *dev; 199362306a36Sopenharmony_ci struct nlmsghdr *nlh; 199462306a36Sopenharmony_ci struct rtmsg *rtm; 199562306a36Sopenharmony_ci 199662306a36Sopenharmony_ci nlh = nlmsg_put(skb, portid, seq, event, sizeof(*rtm), flags); 199762306a36Sopenharmony_ci if (nlh == NULL) 199862306a36Sopenharmony_ci return -EMSGSIZE; 199962306a36Sopenharmony_ci 200062306a36Sopenharmony_ci rtm = nlmsg_data(nlh); 200162306a36Sopenharmony_ci rtm->rtm_family = AF_MPLS; 200262306a36Sopenharmony_ci rtm->rtm_dst_len = 20; 200362306a36Sopenharmony_ci rtm->rtm_src_len = 0; 200462306a36Sopenharmony_ci rtm->rtm_tos = 0; 200562306a36Sopenharmony_ci rtm->rtm_table = RT_TABLE_MAIN; 200662306a36Sopenharmony_ci rtm->rtm_protocol = rt->rt_protocol; 200762306a36Sopenharmony_ci rtm->rtm_scope = RT_SCOPE_UNIVERSE; 200862306a36Sopenharmony_ci rtm->rtm_type = RTN_UNICAST; 200962306a36Sopenharmony_ci rtm->rtm_flags = 0; 201062306a36Sopenharmony_ci 201162306a36Sopenharmony_ci if (nla_put_labels(skb, RTA_DST, 1, &label)) 201262306a36Sopenharmony_ci goto nla_put_failure; 201362306a36Sopenharmony_ci 201462306a36Sopenharmony_ci if (rt->rt_ttl_propagate != MPLS_TTL_PROP_DEFAULT) { 201562306a36Sopenharmony_ci bool ttl_propagate = 201662306a36Sopenharmony_ci rt->rt_ttl_propagate == MPLS_TTL_PROP_ENABLED; 201762306a36Sopenharmony_ci 201862306a36Sopenharmony_ci if (nla_put_u8(skb, RTA_TTL_PROPAGATE, 201962306a36Sopenharmony_ci ttl_propagate)) 202062306a36Sopenharmony_ci goto nla_put_failure; 202162306a36Sopenharmony_ci } 202262306a36Sopenharmony_ci if (rt->rt_nhn == 1) { 202362306a36Sopenharmony_ci const struct mpls_nh *nh = rt->rt_nh; 202462306a36Sopenharmony_ci 202562306a36Sopenharmony_ci if (nh->nh_labels && 202662306a36Sopenharmony_ci nla_put_labels(skb, RTA_NEWDST, nh->nh_labels, 202762306a36Sopenharmony_ci nh->nh_label)) 202862306a36Sopenharmony_ci goto nla_put_failure; 202962306a36Sopenharmony_ci if (nh->nh_via_table != MPLS_NEIGH_TABLE_UNSPEC && 203062306a36Sopenharmony_ci nla_put_via(skb, nh->nh_via_table, mpls_nh_via(rt, nh), 203162306a36Sopenharmony_ci nh->nh_via_alen)) 203262306a36Sopenharmony_ci goto nla_put_failure; 203362306a36Sopenharmony_ci dev = nh->nh_dev; 203462306a36Sopenharmony_ci if (dev && nla_put_u32(skb, RTA_OIF, dev->ifindex)) 203562306a36Sopenharmony_ci goto nla_put_failure; 203662306a36Sopenharmony_ci if (nh->nh_flags & RTNH_F_LINKDOWN) 203762306a36Sopenharmony_ci rtm->rtm_flags |= RTNH_F_LINKDOWN; 203862306a36Sopenharmony_ci if (nh->nh_flags & RTNH_F_DEAD) 203962306a36Sopenharmony_ci rtm->rtm_flags |= RTNH_F_DEAD; 204062306a36Sopenharmony_ci } else { 204162306a36Sopenharmony_ci struct rtnexthop *rtnh; 204262306a36Sopenharmony_ci struct nlattr *mp; 204362306a36Sopenharmony_ci u8 linkdown = 0; 204462306a36Sopenharmony_ci u8 dead = 0; 204562306a36Sopenharmony_ci 204662306a36Sopenharmony_ci mp = nla_nest_start_noflag(skb, RTA_MULTIPATH); 204762306a36Sopenharmony_ci if (!mp) 204862306a36Sopenharmony_ci goto nla_put_failure; 204962306a36Sopenharmony_ci 205062306a36Sopenharmony_ci for_nexthops(rt) { 205162306a36Sopenharmony_ci dev = nh->nh_dev; 205262306a36Sopenharmony_ci if (!dev) 205362306a36Sopenharmony_ci continue; 205462306a36Sopenharmony_ci 205562306a36Sopenharmony_ci rtnh = nla_reserve_nohdr(skb, sizeof(*rtnh)); 205662306a36Sopenharmony_ci if (!rtnh) 205762306a36Sopenharmony_ci goto nla_put_failure; 205862306a36Sopenharmony_ci 205962306a36Sopenharmony_ci rtnh->rtnh_ifindex = dev->ifindex; 206062306a36Sopenharmony_ci if (nh->nh_flags & RTNH_F_LINKDOWN) { 206162306a36Sopenharmony_ci rtnh->rtnh_flags |= RTNH_F_LINKDOWN; 206262306a36Sopenharmony_ci linkdown++; 206362306a36Sopenharmony_ci } 206462306a36Sopenharmony_ci if (nh->nh_flags & RTNH_F_DEAD) { 206562306a36Sopenharmony_ci rtnh->rtnh_flags |= RTNH_F_DEAD; 206662306a36Sopenharmony_ci dead++; 206762306a36Sopenharmony_ci } 206862306a36Sopenharmony_ci 206962306a36Sopenharmony_ci if (nh->nh_labels && nla_put_labels(skb, RTA_NEWDST, 207062306a36Sopenharmony_ci nh->nh_labels, 207162306a36Sopenharmony_ci nh->nh_label)) 207262306a36Sopenharmony_ci goto nla_put_failure; 207362306a36Sopenharmony_ci if (nh->nh_via_table != MPLS_NEIGH_TABLE_UNSPEC && 207462306a36Sopenharmony_ci nla_put_via(skb, nh->nh_via_table, 207562306a36Sopenharmony_ci mpls_nh_via(rt, nh), 207662306a36Sopenharmony_ci nh->nh_via_alen)) 207762306a36Sopenharmony_ci goto nla_put_failure; 207862306a36Sopenharmony_ci 207962306a36Sopenharmony_ci /* length of rtnetlink header + attributes */ 208062306a36Sopenharmony_ci rtnh->rtnh_len = nlmsg_get_pos(skb) - (void *)rtnh; 208162306a36Sopenharmony_ci } endfor_nexthops(rt); 208262306a36Sopenharmony_ci 208362306a36Sopenharmony_ci if (linkdown == rt->rt_nhn) 208462306a36Sopenharmony_ci rtm->rtm_flags |= RTNH_F_LINKDOWN; 208562306a36Sopenharmony_ci if (dead == rt->rt_nhn) 208662306a36Sopenharmony_ci rtm->rtm_flags |= RTNH_F_DEAD; 208762306a36Sopenharmony_ci 208862306a36Sopenharmony_ci nla_nest_end(skb, mp); 208962306a36Sopenharmony_ci } 209062306a36Sopenharmony_ci 209162306a36Sopenharmony_ci nlmsg_end(skb, nlh); 209262306a36Sopenharmony_ci return 0; 209362306a36Sopenharmony_ci 209462306a36Sopenharmony_cinla_put_failure: 209562306a36Sopenharmony_ci nlmsg_cancel(skb, nlh); 209662306a36Sopenharmony_ci return -EMSGSIZE; 209762306a36Sopenharmony_ci} 209862306a36Sopenharmony_ci 209962306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_INET) 210062306a36Sopenharmony_cistatic int mpls_valid_fib_dump_req(struct net *net, const struct nlmsghdr *nlh, 210162306a36Sopenharmony_ci struct fib_dump_filter *filter, 210262306a36Sopenharmony_ci struct netlink_callback *cb) 210362306a36Sopenharmony_ci{ 210462306a36Sopenharmony_ci return ip_valid_fib_dump_req(net, nlh, filter, cb); 210562306a36Sopenharmony_ci} 210662306a36Sopenharmony_ci#else 210762306a36Sopenharmony_cistatic int mpls_valid_fib_dump_req(struct net *net, const struct nlmsghdr *nlh, 210862306a36Sopenharmony_ci struct fib_dump_filter *filter, 210962306a36Sopenharmony_ci struct netlink_callback *cb) 211062306a36Sopenharmony_ci{ 211162306a36Sopenharmony_ci struct netlink_ext_ack *extack = cb->extack; 211262306a36Sopenharmony_ci struct nlattr *tb[RTA_MAX + 1]; 211362306a36Sopenharmony_ci struct rtmsg *rtm; 211462306a36Sopenharmony_ci int err, i; 211562306a36Sopenharmony_ci 211662306a36Sopenharmony_ci if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*rtm))) { 211762306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Invalid header for FIB dump request"); 211862306a36Sopenharmony_ci return -EINVAL; 211962306a36Sopenharmony_ci } 212062306a36Sopenharmony_ci 212162306a36Sopenharmony_ci rtm = nlmsg_data(nlh); 212262306a36Sopenharmony_ci if (rtm->rtm_dst_len || rtm->rtm_src_len || rtm->rtm_tos || 212362306a36Sopenharmony_ci rtm->rtm_table || rtm->rtm_scope || rtm->rtm_type || 212462306a36Sopenharmony_ci rtm->rtm_flags) { 212562306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Invalid values in header for FIB dump request"); 212662306a36Sopenharmony_ci return -EINVAL; 212762306a36Sopenharmony_ci } 212862306a36Sopenharmony_ci 212962306a36Sopenharmony_ci if (rtm->rtm_protocol) { 213062306a36Sopenharmony_ci filter->protocol = rtm->rtm_protocol; 213162306a36Sopenharmony_ci filter->filter_set = 1; 213262306a36Sopenharmony_ci cb->answer_flags = NLM_F_DUMP_FILTERED; 213362306a36Sopenharmony_ci } 213462306a36Sopenharmony_ci 213562306a36Sopenharmony_ci err = nlmsg_parse_deprecated_strict(nlh, sizeof(*rtm), tb, RTA_MAX, 213662306a36Sopenharmony_ci rtm_mpls_policy, extack); 213762306a36Sopenharmony_ci if (err < 0) 213862306a36Sopenharmony_ci return err; 213962306a36Sopenharmony_ci 214062306a36Sopenharmony_ci for (i = 0; i <= RTA_MAX; ++i) { 214162306a36Sopenharmony_ci int ifindex; 214262306a36Sopenharmony_ci 214362306a36Sopenharmony_ci if (i == RTA_OIF) { 214462306a36Sopenharmony_ci ifindex = nla_get_u32(tb[i]); 214562306a36Sopenharmony_ci filter->dev = __dev_get_by_index(net, ifindex); 214662306a36Sopenharmony_ci if (!filter->dev) 214762306a36Sopenharmony_ci return -ENODEV; 214862306a36Sopenharmony_ci filter->filter_set = 1; 214962306a36Sopenharmony_ci } else if (tb[i]) { 215062306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Unsupported attribute in dump request"); 215162306a36Sopenharmony_ci return -EINVAL; 215262306a36Sopenharmony_ci } 215362306a36Sopenharmony_ci } 215462306a36Sopenharmony_ci 215562306a36Sopenharmony_ci return 0; 215662306a36Sopenharmony_ci} 215762306a36Sopenharmony_ci#endif 215862306a36Sopenharmony_ci 215962306a36Sopenharmony_cistatic bool mpls_rt_uses_dev(struct mpls_route *rt, 216062306a36Sopenharmony_ci const struct net_device *dev) 216162306a36Sopenharmony_ci{ 216262306a36Sopenharmony_ci if (rt->rt_nhn == 1) { 216362306a36Sopenharmony_ci struct mpls_nh *nh = rt->rt_nh; 216462306a36Sopenharmony_ci 216562306a36Sopenharmony_ci if (nh->nh_dev == dev) 216662306a36Sopenharmony_ci return true; 216762306a36Sopenharmony_ci } else { 216862306a36Sopenharmony_ci for_nexthops(rt) { 216962306a36Sopenharmony_ci if (nh->nh_dev == dev) 217062306a36Sopenharmony_ci return true; 217162306a36Sopenharmony_ci } endfor_nexthops(rt); 217262306a36Sopenharmony_ci } 217362306a36Sopenharmony_ci 217462306a36Sopenharmony_ci return false; 217562306a36Sopenharmony_ci} 217662306a36Sopenharmony_ci 217762306a36Sopenharmony_cistatic int mpls_dump_routes(struct sk_buff *skb, struct netlink_callback *cb) 217862306a36Sopenharmony_ci{ 217962306a36Sopenharmony_ci const struct nlmsghdr *nlh = cb->nlh; 218062306a36Sopenharmony_ci struct net *net = sock_net(skb->sk); 218162306a36Sopenharmony_ci struct mpls_route __rcu **platform_label; 218262306a36Sopenharmony_ci struct fib_dump_filter filter = {}; 218362306a36Sopenharmony_ci unsigned int flags = NLM_F_MULTI; 218462306a36Sopenharmony_ci size_t platform_labels; 218562306a36Sopenharmony_ci unsigned int index; 218662306a36Sopenharmony_ci 218762306a36Sopenharmony_ci ASSERT_RTNL(); 218862306a36Sopenharmony_ci 218962306a36Sopenharmony_ci if (cb->strict_check) { 219062306a36Sopenharmony_ci int err; 219162306a36Sopenharmony_ci 219262306a36Sopenharmony_ci err = mpls_valid_fib_dump_req(net, nlh, &filter, cb); 219362306a36Sopenharmony_ci if (err < 0) 219462306a36Sopenharmony_ci return err; 219562306a36Sopenharmony_ci 219662306a36Sopenharmony_ci /* for MPLS, there is only 1 table with fixed type and flags. 219762306a36Sopenharmony_ci * If either are set in the filter then return nothing. 219862306a36Sopenharmony_ci */ 219962306a36Sopenharmony_ci if ((filter.table_id && filter.table_id != RT_TABLE_MAIN) || 220062306a36Sopenharmony_ci (filter.rt_type && filter.rt_type != RTN_UNICAST) || 220162306a36Sopenharmony_ci filter.flags) 220262306a36Sopenharmony_ci return skb->len; 220362306a36Sopenharmony_ci } 220462306a36Sopenharmony_ci 220562306a36Sopenharmony_ci index = cb->args[0]; 220662306a36Sopenharmony_ci if (index < MPLS_LABEL_FIRST_UNRESERVED) 220762306a36Sopenharmony_ci index = MPLS_LABEL_FIRST_UNRESERVED; 220862306a36Sopenharmony_ci 220962306a36Sopenharmony_ci platform_label = rtnl_dereference(net->mpls.platform_label); 221062306a36Sopenharmony_ci platform_labels = net->mpls.platform_labels; 221162306a36Sopenharmony_ci 221262306a36Sopenharmony_ci if (filter.filter_set) 221362306a36Sopenharmony_ci flags |= NLM_F_DUMP_FILTERED; 221462306a36Sopenharmony_ci 221562306a36Sopenharmony_ci for (; index < platform_labels; index++) { 221662306a36Sopenharmony_ci struct mpls_route *rt; 221762306a36Sopenharmony_ci 221862306a36Sopenharmony_ci rt = rtnl_dereference(platform_label[index]); 221962306a36Sopenharmony_ci if (!rt) 222062306a36Sopenharmony_ci continue; 222162306a36Sopenharmony_ci 222262306a36Sopenharmony_ci if ((filter.dev && !mpls_rt_uses_dev(rt, filter.dev)) || 222362306a36Sopenharmony_ci (filter.protocol && rt->rt_protocol != filter.protocol)) 222462306a36Sopenharmony_ci continue; 222562306a36Sopenharmony_ci 222662306a36Sopenharmony_ci if (mpls_dump_route(skb, NETLINK_CB(cb->skb).portid, 222762306a36Sopenharmony_ci cb->nlh->nlmsg_seq, RTM_NEWROUTE, 222862306a36Sopenharmony_ci index, rt, flags) < 0) 222962306a36Sopenharmony_ci break; 223062306a36Sopenharmony_ci } 223162306a36Sopenharmony_ci cb->args[0] = index; 223262306a36Sopenharmony_ci 223362306a36Sopenharmony_ci return skb->len; 223462306a36Sopenharmony_ci} 223562306a36Sopenharmony_ci 223662306a36Sopenharmony_cistatic inline size_t lfib_nlmsg_size(struct mpls_route *rt) 223762306a36Sopenharmony_ci{ 223862306a36Sopenharmony_ci size_t payload = 223962306a36Sopenharmony_ci NLMSG_ALIGN(sizeof(struct rtmsg)) 224062306a36Sopenharmony_ci + nla_total_size(4) /* RTA_DST */ 224162306a36Sopenharmony_ci + nla_total_size(1); /* RTA_TTL_PROPAGATE */ 224262306a36Sopenharmony_ci 224362306a36Sopenharmony_ci if (rt->rt_nhn == 1) { 224462306a36Sopenharmony_ci struct mpls_nh *nh = rt->rt_nh; 224562306a36Sopenharmony_ci 224662306a36Sopenharmony_ci if (nh->nh_dev) 224762306a36Sopenharmony_ci payload += nla_total_size(4); /* RTA_OIF */ 224862306a36Sopenharmony_ci if (nh->nh_via_table != MPLS_NEIGH_TABLE_UNSPEC) /* RTA_VIA */ 224962306a36Sopenharmony_ci payload += nla_total_size(2 + nh->nh_via_alen); 225062306a36Sopenharmony_ci if (nh->nh_labels) /* RTA_NEWDST */ 225162306a36Sopenharmony_ci payload += nla_total_size(nh->nh_labels * 4); 225262306a36Sopenharmony_ci } else { 225362306a36Sopenharmony_ci /* each nexthop is packed in an attribute */ 225462306a36Sopenharmony_ci size_t nhsize = 0; 225562306a36Sopenharmony_ci 225662306a36Sopenharmony_ci for_nexthops(rt) { 225762306a36Sopenharmony_ci if (!nh->nh_dev) 225862306a36Sopenharmony_ci continue; 225962306a36Sopenharmony_ci nhsize += nla_total_size(sizeof(struct rtnexthop)); 226062306a36Sopenharmony_ci /* RTA_VIA */ 226162306a36Sopenharmony_ci if (nh->nh_via_table != MPLS_NEIGH_TABLE_UNSPEC) 226262306a36Sopenharmony_ci nhsize += nla_total_size(2 + nh->nh_via_alen); 226362306a36Sopenharmony_ci if (nh->nh_labels) 226462306a36Sopenharmony_ci nhsize += nla_total_size(nh->nh_labels * 4); 226562306a36Sopenharmony_ci } endfor_nexthops(rt); 226662306a36Sopenharmony_ci /* nested attribute */ 226762306a36Sopenharmony_ci payload += nla_total_size(nhsize); 226862306a36Sopenharmony_ci } 226962306a36Sopenharmony_ci 227062306a36Sopenharmony_ci return payload; 227162306a36Sopenharmony_ci} 227262306a36Sopenharmony_ci 227362306a36Sopenharmony_cistatic void rtmsg_lfib(int event, u32 label, struct mpls_route *rt, 227462306a36Sopenharmony_ci struct nlmsghdr *nlh, struct net *net, u32 portid, 227562306a36Sopenharmony_ci unsigned int nlm_flags) 227662306a36Sopenharmony_ci{ 227762306a36Sopenharmony_ci struct sk_buff *skb; 227862306a36Sopenharmony_ci u32 seq = nlh ? nlh->nlmsg_seq : 0; 227962306a36Sopenharmony_ci int err = -ENOBUFS; 228062306a36Sopenharmony_ci 228162306a36Sopenharmony_ci skb = nlmsg_new(lfib_nlmsg_size(rt), GFP_KERNEL); 228262306a36Sopenharmony_ci if (skb == NULL) 228362306a36Sopenharmony_ci goto errout; 228462306a36Sopenharmony_ci 228562306a36Sopenharmony_ci err = mpls_dump_route(skb, portid, seq, event, label, rt, nlm_flags); 228662306a36Sopenharmony_ci if (err < 0) { 228762306a36Sopenharmony_ci /* -EMSGSIZE implies BUG in lfib_nlmsg_size */ 228862306a36Sopenharmony_ci WARN_ON(err == -EMSGSIZE); 228962306a36Sopenharmony_ci kfree_skb(skb); 229062306a36Sopenharmony_ci goto errout; 229162306a36Sopenharmony_ci } 229262306a36Sopenharmony_ci rtnl_notify(skb, net, portid, RTNLGRP_MPLS_ROUTE, nlh, GFP_KERNEL); 229362306a36Sopenharmony_ci 229462306a36Sopenharmony_ci return; 229562306a36Sopenharmony_cierrout: 229662306a36Sopenharmony_ci if (err < 0) 229762306a36Sopenharmony_ci rtnl_set_sk_err(net, RTNLGRP_MPLS_ROUTE, err); 229862306a36Sopenharmony_ci} 229962306a36Sopenharmony_ci 230062306a36Sopenharmony_cistatic int mpls_valid_getroute_req(struct sk_buff *skb, 230162306a36Sopenharmony_ci const struct nlmsghdr *nlh, 230262306a36Sopenharmony_ci struct nlattr **tb, 230362306a36Sopenharmony_ci struct netlink_ext_ack *extack) 230462306a36Sopenharmony_ci{ 230562306a36Sopenharmony_ci struct rtmsg *rtm; 230662306a36Sopenharmony_ci int i, err; 230762306a36Sopenharmony_ci 230862306a36Sopenharmony_ci if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*rtm))) { 230962306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, 231062306a36Sopenharmony_ci "Invalid header for get route request"); 231162306a36Sopenharmony_ci return -EINVAL; 231262306a36Sopenharmony_ci } 231362306a36Sopenharmony_ci 231462306a36Sopenharmony_ci if (!netlink_strict_get_check(skb)) 231562306a36Sopenharmony_ci return nlmsg_parse_deprecated(nlh, sizeof(*rtm), tb, RTA_MAX, 231662306a36Sopenharmony_ci rtm_mpls_policy, extack); 231762306a36Sopenharmony_ci 231862306a36Sopenharmony_ci rtm = nlmsg_data(nlh); 231962306a36Sopenharmony_ci if ((rtm->rtm_dst_len && rtm->rtm_dst_len != 20) || 232062306a36Sopenharmony_ci rtm->rtm_src_len || rtm->rtm_tos || rtm->rtm_table || 232162306a36Sopenharmony_ci rtm->rtm_protocol || rtm->rtm_scope || rtm->rtm_type) { 232262306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Invalid values in header for get route request"); 232362306a36Sopenharmony_ci return -EINVAL; 232462306a36Sopenharmony_ci } 232562306a36Sopenharmony_ci if (rtm->rtm_flags & ~RTM_F_FIB_MATCH) { 232662306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, 232762306a36Sopenharmony_ci "Invalid flags for get route request"); 232862306a36Sopenharmony_ci return -EINVAL; 232962306a36Sopenharmony_ci } 233062306a36Sopenharmony_ci 233162306a36Sopenharmony_ci err = nlmsg_parse_deprecated_strict(nlh, sizeof(*rtm), tb, RTA_MAX, 233262306a36Sopenharmony_ci rtm_mpls_policy, extack); 233362306a36Sopenharmony_ci if (err) 233462306a36Sopenharmony_ci return err; 233562306a36Sopenharmony_ci 233662306a36Sopenharmony_ci if ((tb[RTA_DST] || tb[RTA_NEWDST]) && !rtm->rtm_dst_len) { 233762306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "rtm_dst_len must be 20 for MPLS"); 233862306a36Sopenharmony_ci return -EINVAL; 233962306a36Sopenharmony_ci } 234062306a36Sopenharmony_ci 234162306a36Sopenharmony_ci for (i = 0; i <= RTA_MAX; i++) { 234262306a36Sopenharmony_ci if (!tb[i]) 234362306a36Sopenharmony_ci continue; 234462306a36Sopenharmony_ci 234562306a36Sopenharmony_ci switch (i) { 234662306a36Sopenharmony_ci case RTA_DST: 234762306a36Sopenharmony_ci case RTA_NEWDST: 234862306a36Sopenharmony_ci break; 234962306a36Sopenharmony_ci default: 235062306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Unsupported attribute in get route request"); 235162306a36Sopenharmony_ci return -EINVAL; 235262306a36Sopenharmony_ci } 235362306a36Sopenharmony_ci } 235462306a36Sopenharmony_ci 235562306a36Sopenharmony_ci return 0; 235662306a36Sopenharmony_ci} 235762306a36Sopenharmony_ci 235862306a36Sopenharmony_cistatic int mpls_getroute(struct sk_buff *in_skb, struct nlmsghdr *in_nlh, 235962306a36Sopenharmony_ci struct netlink_ext_ack *extack) 236062306a36Sopenharmony_ci{ 236162306a36Sopenharmony_ci struct net *net = sock_net(in_skb->sk); 236262306a36Sopenharmony_ci u32 portid = NETLINK_CB(in_skb).portid; 236362306a36Sopenharmony_ci u32 in_label = LABEL_NOT_SPECIFIED; 236462306a36Sopenharmony_ci struct nlattr *tb[RTA_MAX + 1]; 236562306a36Sopenharmony_ci u32 labels[MAX_NEW_LABELS]; 236662306a36Sopenharmony_ci struct mpls_shim_hdr *hdr; 236762306a36Sopenharmony_ci unsigned int hdr_size = 0; 236862306a36Sopenharmony_ci const struct mpls_nh *nh; 236962306a36Sopenharmony_ci struct net_device *dev; 237062306a36Sopenharmony_ci struct mpls_route *rt; 237162306a36Sopenharmony_ci struct rtmsg *rtm, *r; 237262306a36Sopenharmony_ci struct nlmsghdr *nlh; 237362306a36Sopenharmony_ci struct sk_buff *skb; 237462306a36Sopenharmony_ci u8 n_labels; 237562306a36Sopenharmony_ci int err; 237662306a36Sopenharmony_ci 237762306a36Sopenharmony_ci err = mpls_valid_getroute_req(in_skb, in_nlh, tb, extack); 237862306a36Sopenharmony_ci if (err < 0) 237962306a36Sopenharmony_ci goto errout; 238062306a36Sopenharmony_ci 238162306a36Sopenharmony_ci rtm = nlmsg_data(in_nlh); 238262306a36Sopenharmony_ci 238362306a36Sopenharmony_ci if (tb[RTA_DST]) { 238462306a36Sopenharmony_ci u8 label_count; 238562306a36Sopenharmony_ci 238662306a36Sopenharmony_ci if (nla_get_labels(tb[RTA_DST], 1, &label_count, 238762306a36Sopenharmony_ci &in_label, extack)) { 238862306a36Sopenharmony_ci err = -EINVAL; 238962306a36Sopenharmony_ci goto errout; 239062306a36Sopenharmony_ci } 239162306a36Sopenharmony_ci 239262306a36Sopenharmony_ci if (!mpls_label_ok(net, &in_label, extack)) { 239362306a36Sopenharmony_ci err = -EINVAL; 239462306a36Sopenharmony_ci goto errout; 239562306a36Sopenharmony_ci } 239662306a36Sopenharmony_ci } 239762306a36Sopenharmony_ci 239862306a36Sopenharmony_ci rt = mpls_route_input_rcu(net, in_label); 239962306a36Sopenharmony_ci if (!rt) { 240062306a36Sopenharmony_ci err = -ENETUNREACH; 240162306a36Sopenharmony_ci goto errout; 240262306a36Sopenharmony_ci } 240362306a36Sopenharmony_ci 240462306a36Sopenharmony_ci if (rtm->rtm_flags & RTM_F_FIB_MATCH) { 240562306a36Sopenharmony_ci skb = nlmsg_new(lfib_nlmsg_size(rt), GFP_KERNEL); 240662306a36Sopenharmony_ci if (!skb) { 240762306a36Sopenharmony_ci err = -ENOBUFS; 240862306a36Sopenharmony_ci goto errout; 240962306a36Sopenharmony_ci } 241062306a36Sopenharmony_ci 241162306a36Sopenharmony_ci err = mpls_dump_route(skb, portid, in_nlh->nlmsg_seq, 241262306a36Sopenharmony_ci RTM_NEWROUTE, in_label, rt, 0); 241362306a36Sopenharmony_ci if (err < 0) { 241462306a36Sopenharmony_ci /* -EMSGSIZE implies BUG in lfib_nlmsg_size */ 241562306a36Sopenharmony_ci WARN_ON(err == -EMSGSIZE); 241662306a36Sopenharmony_ci goto errout_free; 241762306a36Sopenharmony_ci } 241862306a36Sopenharmony_ci 241962306a36Sopenharmony_ci return rtnl_unicast(skb, net, portid); 242062306a36Sopenharmony_ci } 242162306a36Sopenharmony_ci 242262306a36Sopenharmony_ci if (tb[RTA_NEWDST]) { 242362306a36Sopenharmony_ci if (nla_get_labels(tb[RTA_NEWDST], MAX_NEW_LABELS, &n_labels, 242462306a36Sopenharmony_ci labels, extack) != 0) { 242562306a36Sopenharmony_ci err = -EINVAL; 242662306a36Sopenharmony_ci goto errout; 242762306a36Sopenharmony_ci } 242862306a36Sopenharmony_ci 242962306a36Sopenharmony_ci hdr_size = n_labels * sizeof(struct mpls_shim_hdr); 243062306a36Sopenharmony_ci } 243162306a36Sopenharmony_ci 243262306a36Sopenharmony_ci skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL); 243362306a36Sopenharmony_ci if (!skb) { 243462306a36Sopenharmony_ci err = -ENOBUFS; 243562306a36Sopenharmony_ci goto errout; 243662306a36Sopenharmony_ci } 243762306a36Sopenharmony_ci 243862306a36Sopenharmony_ci skb->protocol = htons(ETH_P_MPLS_UC); 243962306a36Sopenharmony_ci 244062306a36Sopenharmony_ci if (hdr_size) { 244162306a36Sopenharmony_ci bool bos; 244262306a36Sopenharmony_ci int i; 244362306a36Sopenharmony_ci 244462306a36Sopenharmony_ci if (skb_cow(skb, hdr_size)) { 244562306a36Sopenharmony_ci err = -ENOBUFS; 244662306a36Sopenharmony_ci goto errout_free; 244762306a36Sopenharmony_ci } 244862306a36Sopenharmony_ci 244962306a36Sopenharmony_ci skb_reserve(skb, hdr_size); 245062306a36Sopenharmony_ci skb_push(skb, hdr_size); 245162306a36Sopenharmony_ci skb_reset_network_header(skb); 245262306a36Sopenharmony_ci 245362306a36Sopenharmony_ci /* Push new labels */ 245462306a36Sopenharmony_ci hdr = mpls_hdr(skb); 245562306a36Sopenharmony_ci bos = true; 245662306a36Sopenharmony_ci for (i = n_labels - 1; i >= 0; i--) { 245762306a36Sopenharmony_ci hdr[i] = mpls_entry_encode(labels[i], 245862306a36Sopenharmony_ci 1, 0, bos); 245962306a36Sopenharmony_ci bos = false; 246062306a36Sopenharmony_ci } 246162306a36Sopenharmony_ci } 246262306a36Sopenharmony_ci 246362306a36Sopenharmony_ci nh = mpls_select_multipath(rt, skb); 246462306a36Sopenharmony_ci if (!nh) { 246562306a36Sopenharmony_ci err = -ENETUNREACH; 246662306a36Sopenharmony_ci goto errout_free; 246762306a36Sopenharmony_ci } 246862306a36Sopenharmony_ci 246962306a36Sopenharmony_ci if (hdr_size) { 247062306a36Sopenharmony_ci skb_pull(skb, hdr_size); 247162306a36Sopenharmony_ci skb_reset_network_header(skb); 247262306a36Sopenharmony_ci } 247362306a36Sopenharmony_ci 247462306a36Sopenharmony_ci nlh = nlmsg_put(skb, portid, in_nlh->nlmsg_seq, 247562306a36Sopenharmony_ci RTM_NEWROUTE, sizeof(*r), 0); 247662306a36Sopenharmony_ci if (!nlh) { 247762306a36Sopenharmony_ci err = -EMSGSIZE; 247862306a36Sopenharmony_ci goto errout_free; 247962306a36Sopenharmony_ci } 248062306a36Sopenharmony_ci 248162306a36Sopenharmony_ci r = nlmsg_data(nlh); 248262306a36Sopenharmony_ci r->rtm_family = AF_MPLS; 248362306a36Sopenharmony_ci r->rtm_dst_len = 20; 248462306a36Sopenharmony_ci r->rtm_src_len = 0; 248562306a36Sopenharmony_ci r->rtm_table = RT_TABLE_MAIN; 248662306a36Sopenharmony_ci r->rtm_type = RTN_UNICAST; 248762306a36Sopenharmony_ci r->rtm_scope = RT_SCOPE_UNIVERSE; 248862306a36Sopenharmony_ci r->rtm_protocol = rt->rt_protocol; 248962306a36Sopenharmony_ci r->rtm_flags = 0; 249062306a36Sopenharmony_ci 249162306a36Sopenharmony_ci if (nla_put_labels(skb, RTA_DST, 1, &in_label)) 249262306a36Sopenharmony_ci goto nla_put_failure; 249362306a36Sopenharmony_ci 249462306a36Sopenharmony_ci if (nh->nh_labels && 249562306a36Sopenharmony_ci nla_put_labels(skb, RTA_NEWDST, nh->nh_labels, 249662306a36Sopenharmony_ci nh->nh_label)) 249762306a36Sopenharmony_ci goto nla_put_failure; 249862306a36Sopenharmony_ci 249962306a36Sopenharmony_ci if (nh->nh_via_table != MPLS_NEIGH_TABLE_UNSPEC && 250062306a36Sopenharmony_ci nla_put_via(skb, nh->nh_via_table, mpls_nh_via(rt, nh), 250162306a36Sopenharmony_ci nh->nh_via_alen)) 250262306a36Sopenharmony_ci goto nla_put_failure; 250362306a36Sopenharmony_ci dev = nh->nh_dev; 250462306a36Sopenharmony_ci if (dev && nla_put_u32(skb, RTA_OIF, dev->ifindex)) 250562306a36Sopenharmony_ci goto nla_put_failure; 250662306a36Sopenharmony_ci 250762306a36Sopenharmony_ci nlmsg_end(skb, nlh); 250862306a36Sopenharmony_ci 250962306a36Sopenharmony_ci err = rtnl_unicast(skb, net, portid); 251062306a36Sopenharmony_cierrout: 251162306a36Sopenharmony_ci return err; 251262306a36Sopenharmony_ci 251362306a36Sopenharmony_cinla_put_failure: 251462306a36Sopenharmony_ci nlmsg_cancel(skb, nlh); 251562306a36Sopenharmony_ci err = -EMSGSIZE; 251662306a36Sopenharmony_cierrout_free: 251762306a36Sopenharmony_ci kfree_skb(skb); 251862306a36Sopenharmony_ci return err; 251962306a36Sopenharmony_ci} 252062306a36Sopenharmony_ci 252162306a36Sopenharmony_cistatic int resize_platform_label_table(struct net *net, size_t limit) 252262306a36Sopenharmony_ci{ 252362306a36Sopenharmony_ci size_t size = sizeof(struct mpls_route *) * limit; 252462306a36Sopenharmony_ci size_t old_limit; 252562306a36Sopenharmony_ci size_t cp_size; 252662306a36Sopenharmony_ci struct mpls_route __rcu **labels = NULL, **old; 252762306a36Sopenharmony_ci struct mpls_route *rt0 = NULL, *rt2 = NULL; 252862306a36Sopenharmony_ci unsigned index; 252962306a36Sopenharmony_ci 253062306a36Sopenharmony_ci if (size) { 253162306a36Sopenharmony_ci labels = kvzalloc(size, GFP_KERNEL); 253262306a36Sopenharmony_ci if (!labels) 253362306a36Sopenharmony_ci goto nolabels; 253462306a36Sopenharmony_ci } 253562306a36Sopenharmony_ci 253662306a36Sopenharmony_ci /* In case the predefined labels need to be populated */ 253762306a36Sopenharmony_ci if (limit > MPLS_LABEL_IPV4NULL) { 253862306a36Sopenharmony_ci struct net_device *lo = net->loopback_dev; 253962306a36Sopenharmony_ci rt0 = mpls_rt_alloc(1, lo->addr_len, 0); 254062306a36Sopenharmony_ci if (IS_ERR(rt0)) 254162306a36Sopenharmony_ci goto nort0; 254262306a36Sopenharmony_ci rt0->rt_nh->nh_dev = lo; 254362306a36Sopenharmony_ci rt0->rt_protocol = RTPROT_KERNEL; 254462306a36Sopenharmony_ci rt0->rt_payload_type = MPT_IPV4; 254562306a36Sopenharmony_ci rt0->rt_ttl_propagate = MPLS_TTL_PROP_DEFAULT; 254662306a36Sopenharmony_ci rt0->rt_nh->nh_via_table = NEIGH_LINK_TABLE; 254762306a36Sopenharmony_ci rt0->rt_nh->nh_via_alen = lo->addr_len; 254862306a36Sopenharmony_ci memcpy(__mpls_nh_via(rt0, rt0->rt_nh), lo->dev_addr, 254962306a36Sopenharmony_ci lo->addr_len); 255062306a36Sopenharmony_ci } 255162306a36Sopenharmony_ci if (limit > MPLS_LABEL_IPV6NULL) { 255262306a36Sopenharmony_ci struct net_device *lo = net->loopback_dev; 255362306a36Sopenharmony_ci rt2 = mpls_rt_alloc(1, lo->addr_len, 0); 255462306a36Sopenharmony_ci if (IS_ERR(rt2)) 255562306a36Sopenharmony_ci goto nort2; 255662306a36Sopenharmony_ci rt2->rt_nh->nh_dev = lo; 255762306a36Sopenharmony_ci rt2->rt_protocol = RTPROT_KERNEL; 255862306a36Sopenharmony_ci rt2->rt_payload_type = MPT_IPV6; 255962306a36Sopenharmony_ci rt2->rt_ttl_propagate = MPLS_TTL_PROP_DEFAULT; 256062306a36Sopenharmony_ci rt2->rt_nh->nh_via_table = NEIGH_LINK_TABLE; 256162306a36Sopenharmony_ci rt2->rt_nh->nh_via_alen = lo->addr_len; 256262306a36Sopenharmony_ci memcpy(__mpls_nh_via(rt2, rt2->rt_nh), lo->dev_addr, 256362306a36Sopenharmony_ci lo->addr_len); 256462306a36Sopenharmony_ci } 256562306a36Sopenharmony_ci 256662306a36Sopenharmony_ci rtnl_lock(); 256762306a36Sopenharmony_ci /* Remember the original table */ 256862306a36Sopenharmony_ci old = rtnl_dereference(net->mpls.platform_label); 256962306a36Sopenharmony_ci old_limit = net->mpls.platform_labels; 257062306a36Sopenharmony_ci 257162306a36Sopenharmony_ci /* Free any labels beyond the new table */ 257262306a36Sopenharmony_ci for (index = limit; index < old_limit; index++) 257362306a36Sopenharmony_ci mpls_route_update(net, index, NULL, NULL); 257462306a36Sopenharmony_ci 257562306a36Sopenharmony_ci /* Copy over the old labels */ 257662306a36Sopenharmony_ci cp_size = size; 257762306a36Sopenharmony_ci if (old_limit < limit) 257862306a36Sopenharmony_ci cp_size = old_limit * sizeof(struct mpls_route *); 257962306a36Sopenharmony_ci 258062306a36Sopenharmony_ci memcpy(labels, old, cp_size); 258162306a36Sopenharmony_ci 258262306a36Sopenharmony_ci /* If needed set the predefined labels */ 258362306a36Sopenharmony_ci if ((old_limit <= MPLS_LABEL_IPV6NULL) && 258462306a36Sopenharmony_ci (limit > MPLS_LABEL_IPV6NULL)) { 258562306a36Sopenharmony_ci RCU_INIT_POINTER(labels[MPLS_LABEL_IPV6NULL], rt2); 258662306a36Sopenharmony_ci rt2 = NULL; 258762306a36Sopenharmony_ci } 258862306a36Sopenharmony_ci 258962306a36Sopenharmony_ci if ((old_limit <= MPLS_LABEL_IPV4NULL) && 259062306a36Sopenharmony_ci (limit > MPLS_LABEL_IPV4NULL)) { 259162306a36Sopenharmony_ci RCU_INIT_POINTER(labels[MPLS_LABEL_IPV4NULL], rt0); 259262306a36Sopenharmony_ci rt0 = NULL; 259362306a36Sopenharmony_ci } 259462306a36Sopenharmony_ci 259562306a36Sopenharmony_ci /* Update the global pointers */ 259662306a36Sopenharmony_ci net->mpls.platform_labels = limit; 259762306a36Sopenharmony_ci rcu_assign_pointer(net->mpls.platform_label, labels); 259862306a36Sopenharmony_ci 259962306a36Sopenharmony_ci rtnl_unlock(); 260062306a36Sopenharmony_ci 260162306a36Sopenharmony_ci mpls_rt_free(rt2); 260262306a36Sopenharmony_ci mpls_rt_free(rt0); 260362306a36Sopenharmony_ci 260462306a36Sopenharmony_ci if (old) { 260562306a36Sopenharmony_ci synchronize_rcu(); 260662306a36Sopenharmony_ci kvfree(old); 260762306a36Sopenharmony_ci } 260862306a36Sopenharmony_ci return 0; 260962306a36Sopenharmony_ci 261062306a36Sopenharmony_cinort2: 261162306a36Sopenharmony_ci mpls_rt_free(rt0); 261262306a36Sopenharmony_cinort0: 261362306a36Sopenharmony_ci kvfree(labels); 261462306a36Sopenharmony_cinolabels: 261562306a36Sopenharmony_ci return -ENOMEM; 261662306a36Sopenharmony_ci} 261762306a36Sopenharmony_ci 261862306a36Sopenharmony_cistatic int mpls_platform_labels(struct ctl_table *table, int write, 261962306a36Sopenharmony_ci void *buffer, size_t *lenp, loff_t *ppos) 262062306a36Sopenharmony_ci{ 262162306a36Sopenharmony_ci struct net *net = table->data; 262262306a36Sopenharmony_ci int platform_labels = net->mpls.platform_labels; 262362306a36Sopenharmony_ci int ret; 262462306a36Sopenharmony_ci struct ctl_table tmp = { 262562306a36Sopenharmony_ci .procname = table->procname, 262662306a36Sopenharmony_ci .data = &platform_labels, 262762306a36Sopenharmony_ci .maxlen = sizeof(int), 262862306a36Sopenharmony_ci .mode = table->mode, 262962306a36Sopenharmony_ci .extra1 = SYSCTL_ZERO, 263062306a36Sopenharmony_ci .extra2 = &label_limit, 263162306a36Sopenharmony_ci }; 263262306a36Sopenharmony_ci 263362306a36Sopenharmony_ci ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos); 263462306a36Sopenharmony_ci 263562306a36Sopenharmony_ci if (write && ret == 0) 263662306a36Sopenharmony_ci ret = resize_platform_label_table(net, platform_labels); 263762306a36Sopenharmony_ci 263862306a36Sopenharmony_ci return ret; 263962306a36Sopenharmony_ci} 264062306a36Sopenharmony_ci 264162306a36Sopenharmony_ci#define MPLS_NS_SYSCTL_OFFSET(field) \ 264262306a36Sopenharmony_ci (&((struct net *)0)->field) 264362306a36Sopenharmony_ci 264462306a36Sopenharmony_cistatic const struct ctl_table mpls_table[] = { 264562306a36Sopenharmony_ci { 264662306a36Sopenharmony_ci .procname = "platform_labels", 264762306a36Sopenharmony_ci .data = NULL, 264862306a36Sopenharmony_ci .maxlen = sizeof(int), 264962306a36Sopenharmony_ci .mode = 0644, 265062306a36Sopenharmony_ci .proc_handler = mpls_platform_labels, 265162306a36Sopenharmony_ci }, 265262306a36Sopenharmony_ci { 265362306a36Sopenharmony_ci .procname = "ip_ttl_propagate", 265462306a36Sopenharmony_ci .data = MPLS_NS_SYSCTL_OFFSET(mpls.ip_ttl_propagate), 265562306a36Sopenharmony_ci .maxlen = sizeof(int), 265662306a36Sopenharmony_ci .mode = 0644, 265762306a36Sopenharmony_ci .proc_handler = proc_dointvec_minmax, 265862306a36Sopenharmony_ci .extra1 = SYSCTL_ZERO, 265962306a36Sopenharmony_ci .extra2 = SYSCTL_ONE, 266062306a36Sopenharmony_ci }, 266162306a36Sopenharmony_ci { 266262306a36Sopenharmony_ci .procname = "default_ttl", 266362306a36Sopenharmony_ci .data = MPLS_NS_SYSCTL_OFFSET(mpls.default_ttl), 266462306a36Sopenharmony_ci .maxlen = sizeof(int), 266562306a36Sopenharmony_ci .mode = 0644, 266662306a36Sopenharmony_ci .proc_handler = proc_dointvec_minmax, 266762306a36Sopenharmony_ci .extra1 = SYSCTL_ONE, 266862306a36Sopenharmony_ci .extra2 = &ttl_max, 266962306a36Sopenharmony_ci }, 267062306a36Sopenharmony_ci { } 267162306a36Sopenharmony_ci}; 267262306a36Sopenharmony_ci 267362306a36Sopenharmony_cistatic int mpls_net_init(struct net *net) 267462306a36Sopenharmony_ci{ 267562306a36Sopenharmony_ci struct ctl_table *table; 267662306a36Sopenharmony_ci int i; 267762306a36Sopenharmony_ci 267862306a36Sopenharmony_ci net->mpls.platform_labels = 0; 267962306a36Sopenharmony_ci net->mpls.platform_label = NULL; 268062306a36Sopenharmony_ci net->mpls.ip_ttl_propagate = 1; 268162306a36Sopenharmony_ci net->mpls.default_ttl = 255; 268262306a36Sopenharmony_ci 268362306a36Sopenharmony_ci table = kmemdup(mpls_table, sizeof(mpls_table), GFP_KERNEL); 268462306a36Sopenharmony_ci if (table == NULL) 268562306a36Sopenharmony_ci return -ENOMEM; 268662306a36Sopenharmony_ci 268762306a36Sopenharmony_ci /* Table data contains only offsets relative to the base of 268862306a36Sopenharmony_ci * the mdev at this point, so make them absolute. 268962306a36Sopenharmony_ci */ 269062306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(mpls_table) - 1; i++) 269162306a36Sopenharmony_ci table[i].data = (char *)net + (uintptr_t)table[i].data; 269262306a36Sopenharmony_ci 269362306a36Sopenharmony_ci net->mpls.ctl = register_net_sysctl_sz(net, "net/mpls", table, 269462306a36Sopenharmony_ci ARRAY_SIZE(mpls_table)); 269562306a36Sopenharmony_ci if (net->mpls.ctl == NULL) { 269662306a36Sopenharmony_ci kfree(table); 269762306a36Sopenharmony_ci return -ENOMEM; 269862306a36Sopenharmony_ci } 269962306a36Sopenharmony_ci 270062306a36Sopenharmony_ci return 0; 270162306a36Sopenharmony_ci} 270262306a36Sopenharmony_ci 270362306a36Sopenharmony_cistatic void mpls_net_exit(struct net *net) 270462306a36Sopenharmony_ci{ 270562306a36Sopenharmony_ci struct mpls_route __rcu **platform_label; 270662306a36Sopenharmony_ci size_t platform_labels; 270762306a36Sopenharmony_ci struct ctl_table *table; 270862306a36Sopenharmony_ci unsigned int index; 270962306a36Sopenharmony_ci 271062306a36Sopenharmony_ci table = net->mpls.ctl->ctl_table_arg; 271162306a36Sopenharmony_ci unregister_net_sysctl_table(net->mpls.ctl); 271262306a36Sopenharmony_ci kfree(table); 271362306a36Sopenharmony_ci 271462306a36Sopenharmony_ci /* An rcu grace period has passed since there was a device in 271562306a36Sopenharmony_ci * the network namespace (and thus the last in flight packet) 271662306a36Sopenharmony_ci * left this network namespace. This is because 271762306a36Sopenharmony_ci * unregister_netdevice_many and netdev_run_todo has completed 271862306a36Sopenharmony_ci * for each network device that was in this network namespace. 271962306a36Sopenharmony_ci * 272062306a36Sopenharmony_ci * As such no additional rcu synchronization is necessary when 272162306a36Sopenharmony_ci * freeing the platform_label table. 272262306a36Sopenharmony_ci */ 272362306a36Sopenharmony_ci rtnl_lock(); 272462306a36Sopenharmony_ci platform_label = rtnl_dereference(net->mpls.platform_label); 272562306a36Sopenharmony_ci platform_labels = net->mpls.platform_labels; 272662306a36Sopenharmony_ci for (index = 0; index < platform_labels; index++) { 272762306a36Sopenharmony_ci struct mpls_route *rt = rtnl_dereference(platform_label[index]); 272862306a36Sopenharmony_ci RCU_INIT_POINTER(platform_label[index], NULL); 272962306a36Sopenharmony_ci mpls_notify_route(net, index, rt, NULL, NULL); 273062306a36Sopenharmony_ci mpls_rt_free(rt); 273162306a36Sopenharmony_ci } 273262306a36Sopenharmony_ci rtnl_unlock(); 273362306a36Sopenharmony_ci 273462306a36Sopenharmony_ci kvfree(platform_label); 273562306a36Sopenharmony_ci} 273662306a36Sopenharmony_ci 273762306a36Sopenharmony_cistatic struct pernet_operations mpls_net_ops = { 273862306a36Sopenharmony_ci .init = mpls_net_init, 273962306a36Sopenharmony_ci .exit = mpls_net_exit, 274062306a36Sopenharmony_ci}; 274162306a36Sopenharmony_ci 274262306a36Sopenharmony_cistatic struct rtnl_af_ops mpls_af_ops __read_mostly = { 274362306a36Sopenharmony_ci .family = AF_MPLS, 274462306a36Sopenharmony_ci .fill_stats_af = mpls_fill_stats_af, 274562306a36Sopenharmony_ci .get_stats_af_size = mpls_get_stats_af_size, 274662306a36Sopenharmony_ci}; 274762306a36Sopenharmony_ci 274862306a36Sopenharmony_cistatic int __init mpls_init(void) 274962306a36Sopenharmony_ci{ 275062306a36Sopenharmony_ci int err; 275162306a36Sopenharmony_ci 275262306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(struct mpls_shim_hdr) != 4); 275362306a36Sopenharmony_ci 275462306a36Sopenharmony_ci err = register_pernet_subsys(&mpls_net_ops); 275562306a36Sopenharmony_ci if (err) 275662306a36Sopenharmony_ci goto out; 275762306a36Sopenharmony_ci 275862306a36Sopenharmony_ci err = register_netdevice_notifier(&mpls_dev_notifier); 275962306a36Sopenharmony_ci if (err) 276062306a36Sopenharmony_ci goto out_unregister_pernet; 276162306a36Sopenharmony_ci 276262306a36Sopenharmony_ci dev_add_pack(&mpls_packet_type); 276362306a36Sopenharmony_ci 276462306a36Sopenharmony_ci rtnl_af_register(&mpls_af_ops); 276562306a36Sopenharmony_ci 276662306a36Sopenharmony_ci rtnl_register_module(THIS_MODULE, PF_MPLS, RTM_NEWROUTE, 276762306a36Sopenharmony_ci mpls_rtm_newroute, NULL, 0); 276862306a36Sopenharmony_ci rtnl_register_module(THIS_MODULE, PF_MPLS, RTM_DELROUTE, 276962306a36Sopenharmony_ci mpls_rtm_delroute, NULL, 0); 277062306a36Sopenharmony_ci rtnl_register_module(THIS_MODULE, PF_MPLS, RTM_GETROUTE, 277162306a36Sopenharmony_ci mpls_getroute, mpls_dump_routes, 0); 277262306a36Sopenharmony_ci rtnl_register_module(THIS_MODULE, PF_MPLS, RTM_GETNETCONF, 277362306a36Sopenharmony_ci mpls_netconf_get_devconf, 277462306a36Sopenharmony_ci mpls_netconf_dump_devconf, 0); 277562306a36Sopenharmony_ci err = ipgre_tunnel_encap_add_mpls_ops(); 277662306a36Sopenharmony_ci if (err) 277762306a36Sopenharmony_ci pr_err("Can't add mpls over gre tunnel ops\n"); 277862306a36Sopenharmony_ci 277962306a36Sopenharmony_ci err = 0; 278062306a36Sopenharmony_ciout: 278162306a36Sopenharmony_ci return err; 278262306a36Sopenharmony_ci 278362306a36Sopenharmony_ciout_unregister_pernet: 278462306a36Sopenharmony_ci unregister_pernet_subsys(&mpls_net_ops); 278562306a36Sopenharmony_ci goto out; 278662306a36Sopenharmony_ci} 278762306a36Sopenharmony_cimodule_init(mpls_init); 278862306a36Sopenharmony_ci 278962306a36Sopenharmony_cistatic void __exit mpls_exit(void) 279062306a36Sopenharmony_ci{ 279162306a36Sopenharmony_ci rtnl_unregister_all(PF_MPLS); 279262306a36Sopenharmony_ci rtnl_af_unregister(&mpls_af_ops); 279362306a36Sopenharmony_ci dev_remove_pack(&mpls_packet_type); 279462306a36Sopenharmony_ci unregister_netdevice_notifier(&mpls_dev_notifier); 279562306a36Sopenharmony_ci unregister_pernet_subsys(&mpls_net_ops); 279662306a36Sopenharmony_ci ipgre_tunnel_encap_del_mpls_ops(); 279762306a36Sopenharmony_ci} 279862306a36Sopenharmony_cimodule_exit(mpls_exit); 279962306a36Sopenharmony_ci 280062306a36Sopenharmony_ciMODULE_DESCRIPTION("MultiProtocol Label Switching"); 280162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 280262306a36Sopenharmony_ciMODULE_ALIAS_NETPROTO(PF_MPLS); 2803