/* Hey Emacs use -*- mode: C -*- */ /* * Copyright 2021 Cisco and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NUD_VALID #define NUD_VALID \ (NUD_PERMANENT | NUD_NOARP | NUD_REACHABLE | NUD_PROBE | NUD_STALE | \ NUD_DELAY) #endif static void lcp_nl_mk_ip_addr (const struct nl_addr *rna, ip_address_t *ia) { ip_address_reset (ia); ip_address_set (ia, nl_addr_get_binary_addr (rna), nl_addr_get_family (rna) == AF_INET6 ? AF_IP6 : AF_IP4); } static void lcp_nl_mk_mac_addr (const struct nl_addr *rna, mac_address_t *mac) { mac_address_from_bytes (mac, nl_addr_get_binary_addr (rna)); } static int vnet_sw_interface_subid_exists (vnet_main_t *vnm, u32 sw_if_index, u32 id) { u64 sup_and_sub_key = ((u64) (sw_if_index) << 32) | (u64) id; vnet_interface_main_t *im = &vnm->interface_main; uword *p; p = hash_get_mem (im->sw_if_index_by_sup_and_sub, &sup_and_sub_key); if (p) return 1; return 0; } static int vnet_sw_interface_get_available_subid (vnet_main_t *vnm, u32 sw_if_index, u32 *id) { u32 i; for (i = 1; i < 4096; i++) { if (!vnet_sw_interface_subid_exists (vnm, sw_if_index, i)) { *id = i; return 0; } } *id = -1; return 1; } // Returns the LIP for a newly created sub-int pair, or // NULL in case no sub-int could be created. static lcp_itf_pair_t * lcp_nl_link_add_vlan (struct rtnl_link *rl) { vnet_main_t *vnm = vnet_get_main (); int parent_idx, idx; lcp_itf_pair_t *parent_lip, *phy_lip; vnet_sw_interface_t *parent_sw; int vlan; u32 proto; u32 subid; u32 inner_vlan, outer_vlan, flags; u32 phy_sw_if_index, host_sw_if_index; lcp_main_t *lcpm = &lcp_main; u8 old_lcp_auto_subint; if (!rtnl_link_is_vlan (rl)) return NULL; idx = rtnl_link_get_ifindex (rl); parent_idx = rtnl_link_get_link (rl); vlan = rtnl_link_vlan_get_id (rl); proto = rtnl_link_vlan_get_protocol (rl); /* Get the LIP of the parent, can be a phy Te3/0/0 or a subint Te3/0/0.1000 */ if (!(parent_lip = lcp_itf_pair_get (lcp_itf_pair_find_by_vif (parent_idx)))) { NL_WARN ("link_add_vlan: no LCP for parent of %U", format_nl_object, rl); return NULL; } parent_sw = vnet_get_sw_interface (vnm, parent_lip->lip_phy_sw_if_index); if (!parent_sw) { NL_ERROR ("link_add_vlan: Cannot get parent of %U", format_lcp_itf_pair, parent_lip); return NULL; } /* Get the LIP of the phy, ie "phy TenGigabitEthernet3/0/0 host tap1 host-if * e0" */ phy_lip = lcp_itf_pair_get (lcp_itf_pair_find_by_phy (parent_sw->sup_sw_if_index)); if (vnet_sw_interface_is_sub (vnm, parent_lip->lip_phy_sw_if_index)) { // QinQ or QinAD inner_vlan = vlan; outer_vlan = parent_sw->sub.eth.outer_vlan_id; if (ntohs (proto) == ETH_P_8021AD) { NL_ERROR ("link_add_vlan: cannot create inner dot1ad: %U", format_nl_object, rl); return NULL; } } else { inner_vlan = 0; outer_vlan = vlan; } // Flags: no_tags(1), one_tag(2), two_tags(4), dot1ad(8), exact_match(16) see // vnet/interface.h flags = 16; // exact-match if ((parent_sw->sub.eth.flags.dot1ad) || (ntohs (proto) == ETH_P_8021AD)) flags += 8; // dot1ad if (inner_vlan) flags += 4; // two_tags else flags += 2; // one_tag /* Create sub on the phy and on the tap, but avoid triggering sub-int * autocreation if it's enabled. */ old_lcp_auto_subint = lcpm->lcp_auto_subint; lcpm->lcp_auto_subint = 0; /* Generate a subid, take the first available one */ if (vnet_sw_interface_get_available_subid (vnm, parent_sw->sup_sw_if_index, &subid)) { NL_ERROR ("link_add_vlan: cannot find available subid on phy %U", format_vnet_sw_if_index_name, vnm, parent_sw->sup_sw_if_index); lcpm->lcp_auto_subint = old_lcp_auto_subint; return NULL; } vlib_worker_thread_barrier_sync (vlib_get_main ()); NL_NOTICE ( "link_add_vlan: creating subid %u outer %u inner %u flags %u on phy %U", subid, outer_vlan, inner_vlan, flags, format_vnet_sw_if_index_name, vnm, parent_sw->sup_sw_if_index); if (vnet_create_sub_interface (parent_sw->sup_sw_if_index, subid, flags, inner_vlan, outer_vlan, &phy_sw_if_index)) { NL_ERROR ("link_add_vlan: cannot create sub-int on phy %U flags %u " "inner-dot1q %u dot1%s %u", format_vnet_sw_if_index_name, vnm, parent_sw->sup_sw_if_index, flags, inner_vlan, parent_sw->sub.eth.flags.dot1ad ? "ad" : "q", outer_vlan); vlib_worker_thread_barrier_release (vlib_get_main ()); lcpm->lcp_auto_subint = old_lcp_auto_subint; return NULL; } /* Try to use the same subid on the TAP, generate a unique one otherwise. */ if (vnet_sw_interface_subid_exists (vnm, phy_lip->lip_host_sw_if_index, subid) && vnet_sw_interface_get_available_subid ( vnm, phy_lip->lip_host_sw_if_index, &subid)) { NL_ERROR ("link_add_vlan: cannot find available subid on host %U", format_vnet_sw_if_index_name, vnm, phy_lip->lip_host_sw_if_index); vlib_worker_thread_barrier_release (vlib_get_main ()); lcpm->lcp_auto_subint = old_lcp_auto_subint; return NULL; } NL_NOTICE ("link_add_vlan: creating subid %u outer %u inner %u flags %u on " "host %U phy %U", subid, outer_vlan, inner_vlan, flags, format_vnet_sw_if_index_name, vnm, parent_lip->lip_host_sw_if_index, format_vnet_sw_if_index_name, vnm, phy_lip->lip_host_sw_if_index); if (vnet_create_sub_interface (phy_lip->lip_host_sw_if_index, subid, flags, inner_vlan, outer_vlan, &host_sw_if_index)) { NL_ERROR ("link_add_vlan: cannot create sub-int on host %U flags %u " "inner-dot1q %u dot1%s %u", format_vnet_sw_if_index_name, vnm, phy_lip->lip_host_sw_if_index, flags, inner_vlan, parent_sw->sub.eth.flags.dot1ad ? "ad" : "q", outer_vlan); vlib_worker_thread_barrier_release (vlib_get_main ()); lcpm->lcp_auto_subint = old_lcp_auto_subint; return NULL; } NL_NOTICE ("link_add_vlan: Creating LCP for host %U phy %U name %s idx %d", format_vnet_sw_if_index_name, vnm, host_sw_if_index, format_vnet_sw_if_index_name, vnm, phy_sw_if_index, rtnl_link_get_name (rl), idx); vlib_worker_thread_barrier_release (vlib_get_main ()); lcpm->lcp_auto_subint = old_lcp_auto_subint; u8 *if_namev = 0; char *if_name; if_name = rtnl_link_get_name (rl); vec_validate_init_c_string (if_namev, if_name, strnlen (if_name, IFNAMSIZ)); lcp_itf_pair_add (host_sw_if_index, phy_sw_if_index, if_namev, idx, phy_lip->lip_host_type, phy_lip->lip_namespace); vec_free (if_namev); // If all went well, we just created a new LIP and added it to the index -- // so return that new (sub-interface) LIP to the caller. return lcp_itf_pair_get (lcp_itf_pair_find_by_phy (phy_sw_if_index)); } void lcp_nl_link_del (struct rtnl_link *rl) { lcp_itf_pair_t *lip; NL_DBG ("link_del: netlink %U", format_nl_object, rl); if (!(lip = lcp_itf_pair_get ( lcp_itf_pair_find_by_vif (rtnl_link_get_ifindex (rl))))) { NL_WARN ("link_del: no LCP for %U ", format_nl_object, rl); return; } NL_NOTICE ("link_del: Removing %U", format_lcp_itf_pair, lip); vlib_worker_thread_barrier_sync (vlib_get_main ()); lcp_itf_pair_delete (lip->lip_phy_sw_if_index); vlib_worker_thread_barrier_release (vlib_get_main ()); if (rtnl_link_is_vlan (rl)) { NL_NOTICE ("link_del: Removing subint %U", format_vnet_sw_if_index_name, vnet_get_main (), lip->lip_phy_sw_if_index); vlib_worker_thread_barrier_sync (vlib_get_main ()); vnet_delete_sub_interface (lip->lip_phy_sw_if_index); vnet_delete_sub_interface (lip->lip_host_sw_if_index); vlib_worker_thread_barrier_release (vlib_get_main ()); } return; } static void lcp_nl_link_set_mtu (struct rtnl_link *rl, lcp_itf_pair_t *lip) { vnet_main_t *vnm = vnet_get_main (); u32 mtu; vnet_sw_interface_t *sw; vnet_hw_interface_t *hw; mtu = rtnl_link_get_mtu (rl); if (!mtu) return; sw = vnet_get_sw_interface (vnm, lip->lip_phy_sw_if_index); hw = vnet_get_sup_hw_interface (vnm, lip->lip_phy_sw_if_index); if (!sw || !hw) return; /* Set the MTU on the TAP and sw */ vnet_sw_interface_set_mtu (vnm, lip->lip_host_sw_if_index, mtu); vnet_sw_interface_set_mtu (vnm, lip->lip_phy_sw_if_index, mtu); } static void lcp_nl_link_set_lladdr (struct rtnl_link *rl, lcp_itf_pair_t *lip) { vnet_main_t *vnm = vnet_get_main (); struct nl_addr *mac_addr; vnet_sw_interface_t *sw; vnet_hw_interface_t *hw; void *mac_addr_bytes; mac_addr = rtnl_link_get_addr (rl); if (!mac_addr || (nl_addr_get_family (mac_addr) != AF_LLC)) return; sw = vnet_get_sw_interface (vnm, lip->lip_phy_sw_if_index); hw = vnet_get_sup_hw_interface (vnm, lip->lip_phy_sw_if_index); if (!sw || !hw) return; /* can only change address on hw interface */ if (sw->sw_if_index != sw->sup_sw_if_index) return; /* can only change if there's an address present */ if (!vec_len (hw->hw_address)) return; mac_addr_bytes = nl_addr_get_binary_addr (mac_addr); if (clib_memcmp (mac_addr_bytes, hw->hw_address, nl_addr_get_len (mac_addr))) vnet_hw_interface_change_mac_address (vnm, hw->hw_if_index, mac_addr_bytes); /* mcast adjacencies need to be updated */ vnet_update_adjacency_for_sw_interface (vnm, lip->lip_phy_sw_if_index, lip->lip_phy_adjs.adj_index[AF_IP4]); vnet_update_adjacency_for_sw_interface (vnm, lip->lip_phy_sw_if_index, lip->lip_phy_adjs.adj_index[AF_IP6]); } void lcp_nl_link_add (struct rtnl_link *rl, void *ctx) { vnet_main_t *vnm = vnet_get_main (); lcp_itf_pair_t *lip; int admin_state; NL_DBG ("link_add: netlink %U", format_nl_object, rl); /* For NEWLINK messages, if this interface doesn't have a LIP, it * may be a request to create a sub-int; so we call add_vlan() * to create it and pass its new LIP so we can finish the request. */ if (!(lip = lcp_itf_pair_get ( lcp_itf_pair_find_by_vif (rtnl_link_get_ifindex (rl))))) { if (!(lip = lcp_nl_link_add_vlan (rl))) return; } admin_state = (IFF_UP & rtnl_link_get_flags (rl)); vlib_worker_thread_barrier_sync (vlib_get_main ()); if (admin_state) { vnet_sw_interface_admin_up (vnm, lip->lip_host_sw_if_index); vnet_sw_interface_admin_up (vnm, lip->lip_phy_sw_if_index); } else { vnet_sw_interface_admin_down (vnm, lip->lip_phy_sw_if_index); vnet_sw_interface_admin_down (vnm, lip->lip_host_sw_if_index); } lcp_nl_link_set_mtu (rl, lip); lcp_nl_link_set_lladdr (rl, lip); vlib_worker_thread_barrier_release (vlib_get_main ()); NL_NOTICE ("link_add: %U admin %s", format_lcp_itf_pair, lip, admin_state ? "up" : "down"); return; } static const mfib_prefix_t ip4_specials[] = { /* ALL prefixes are in network order */ { /* (*,224.0.0.0)/24 - all local subnet */ .fp_grp_addr = { .ip4.data_u32 = 0x000000e0, }, .fp_len = 24, .fp_proto = FIB_PROTOCOL_IP4, }, }; static void lcp_nl_ip4_mroutes_add_del (u32 sw_if_index, u8 is_add) { const fib_route_path_t path = { .frp_proto = DPO_PROTO_IP4, .frp_addr = zero_addr, .frp_sw_if_index = sw_if_index, .frp_fib_index = ~0, .frp_weight = 1, .frp_mitf_flags = MFIB_ITF_FLAG_ACCEPT, }; u32 mfib_index; int ii; mfib_index = mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, sw_if_index); for (ii = 0; ii < ARRAY_LEN (ip4_specials); ii++) { if (is_add) { mfib_table_entry_path_update (mfib_index, &ip4_specials[ii], MFIB_SOURCE_PLUGIN_LOW, &path); } else { mfib_table_entry_path_remove (mfib_index, &ip4_specials[ii], MFIB_SOURCE_PLUGIN_LOW, &path); } } } static const mfib_prefix_t ip6_specials[] = { /* ALL prefixes are in network order */ { /* (*,ff00::)/8 - all local subnet */ .fp_grp_addr = { .ip6.as_u64[0] = 0x00000000000000ff, }, .fp_len = 8, .fp_proto = FIB_PROTOCOL_IP6, }, }; static void lcp_nl_ip6_mroutes_add_del (u32 sw_if_index, u8 is_add) { const fib_route_path_t path = { .frp_proto = DPO_PROTO_IP6, .frp_addr = zero_addr, .frp_sw_if_index = sw_if_index, .frp_fib_index = ~0, .frp_weight = 1, .frp_mitf_flags = MFIB_ITF_FLAG_ACCEPT, }; u32 mfib_index; int ii; mfib_index = mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6, sw_if_index); for (ii = 0; ii < ARRAY_LEN (ip6_specials); ii++) { if (is_add) { mfib_table_entry_path_update (mfib_index, &ip6_specials[ii], MFIB_SOURCE_PLUGIN_LOW, &path); } else { mfib_table_entry_path_remove (mfib_index, &ip6_specials[ii], MFIB_SOURCE_PLUGIN_LOW, &path); } } } static void lcp_nl_addr_add_del (struct rtnl_addr *ra, int is_del) { lcp_itf_pair_t *lip; ip_address_t nh; NL_DBG ("addr_%s: netlink %U", is_del ? "del" : "add", format_nl_object, ra); if (!(lip = lcp_itf_pair_get ( lcp_itf_pair_find_by_vif (rtnl_addr_get_ifindex (ra))))) { NL_WARN ("addr_%s: no LCP for %U ", is_del ? "del" : "add", format_nl_object, ra); return; } lcp_nl_mk_ip_addr (rtnl_addr_get_local (ra), &nh); vlib_worker_thread_barrier_sync (vlib_get_main ()); if (AF_IP4 == ip_addr_version (&nh)) { ip4_add_del_interface_address ( vlib_get_main (), lip->lip_phy_sw_if_index, &ip_addr_v4 (&nh), rtnl_addr_get_prefixlen (ra), is_del); lcp_nl_ip4_mroutes_add_del (lip->lip_phy_sw_if_index, !is_del); } else if (AF_IP6 == ip_addr_version (&nh)) { if (ip6_address_is_link_local_unicast (&ip_addr_v6 (&nh))) if (is_del) ip6_link_disable (lip->lip_phy_sw_if_index); else { ip6_link_enable (lip->lip_phy_sw_if_index, NULL); ip6_link_set_local_address (lip->lip_phy_sw_if_index, &ip_addr_v6 (&nh)); } else ip6_add_del_interface_address ( vlib_get_main (), lip->lip_phy_sw_if_index, &ip_addr_v6 (&nh), rtnl_addr_get_prefixlen (ra), is_del); lcp_nl_ip6_mroutes_add_del (lip->lip_phy_sw_if_index, !is_del); } vlib_worker_thread_barrier_release (vlib_get_main ()); NL_NOTICE ("addr_%s %U/%d iface %U", is_del ? "del: Deleted" : "add: Added", format_ip_address, &nh, rtnl_addr_get_prefixlen (ra), format_vnet_sw_if_index_name, vnet_get_main (), lip->lip_phy_sw_if_index); } void lcp_nl_addr_add (struct rtnl_addr *ra) { lcp_nl_addr_add_del (ra, 0); } void lcp_nl_addr_del (struct rtnl_addr *ra) { lcp_nl_addr_add_del (ra, 1); } void lcp_nl_neigh_add (struct rtnl_neigh *rn) { lcp_itf_pair_t *lip; struct nl_addr *ll; ip_address_t nh; int state; NL_DBG ("neigh_add: netlink %U", format_nl_object, rn); if (!(lip = lcp_itf_pair_get ( lcp_itf_pair_find_by_vif (rtnl_neigh_get_ifindex (rn))))) { NL_WARN ("neigh_add: no LCP for %U ", format_nl_object, rn); return; } lcp_nl_mk_ip_addr (rtnl_neigh_get_dst (rn), &nh); ll = rtnl_neigh_get_lladdr (rn); state = rtnl_neigh_get_state (rn); if (ll && (state & NUD_VALID)) { mac_address_t mac; ip_neighbor_flags_t flags; int rv; lcp_nl_mk_mac_addr (ll, &mac); if (state & (NUD_NOARP | NUD_PERMANENT)) flags = IP_NEIGHBOR_FLAG_STATIC; else flags = IP_NEIGHBOR_FLAG_DYNAMIC; vlib_worker_thread_barrier_sync (vlib_get_main ()); rv = ip_neighbor_add (&nh, &mac, lip->lip_phy_sw_if_index, flags, NULL); vlib_worker_thread_barrier_release (vlib_get_main ()); if (rv) { NL_ERROR ("neigh_add: Failed %U lladdr %U iface %U", format_ip_address, &nh, format_mac_address, &mac, format_vnet_sw_if_index_name, vnet_get_main (), lip->lip_phy_sw_if_index); } else { NL_NOTICE ("neigh_add: Added %U lladdr %U iface %U", format_ip_address, &nh, format_mac_address, &mac, format_vnet_sw_if_index_name, vnet_get_main (), lip->lip_phy_sw_if_index); } } } void lcp_nl_neigh_del (struct rtnl_neigh *rn) { ip_address_t nh; int rv; NL_DBG ("neigh_del: netlink %U", format_nl_object, rn); lcp_itf_pair_t *lip; if (!(lip = lcp_itf_pair_get ( lcp_itf_pair_find_by_vif (rtnl_neigh_get_ifindex (rn))))) { NL_WARN ("neigh_del: no LCP for %U ", format_nl_object, rn); return; } lcp_nl_mk_ip_addr (rtnl_neigh_get_dst (rn), &nh); vlib_worker_thread_barrier_sync (vlib_get_main ()); rv = ip_neighbor_del (&nh, lip->lip_phy_sw_if_index); vlib_worker_thread_barrier_release (vlib_get_main ()); if (rv == 0 || rv == VNET_API_ERROR_NO_SUCH_ENTRY) { NL_NOTICE ("neigh_del: Deleted %U iface %U", format_ip_address, &nh, format_vnet_sw_if_index_name, vnet_get_main (), lip->lip_phy_sw_if_index); } else { NL_ERROR ("neigh_del: Failed %U iface %U", format_ip_address, &nh, format_vnet_sw_if_index_name, vnet_get_main (), lip->lip_phy_sw_if_index); } }