From 2b93fbe0fec436043a18e5f73d9b994ceda12cbd Mon Sep 17 00:00:00 2001 From: dypet Date: Wed, 8 Jul 2026 14:09:28 -0400 Subject: [PATCH 1/3] Apply changes from PR 1959. Signed-off-by: dypet --- vslib/vpp/SwitchVppFdb.cpp | 457 ++++++++++++++++++++++++++++--- vslib/vpp/SwitchVppHostif.cpp | 1 + vslib/vpp/SwitchVppRif.cpp | 32 ++- vslib/vpp/vppxlate/SaiVppXlate.c | 457 ++++++++++++++++++++++++++++++- vslib/vpp/vppxlate/SaiVppXlate.h | 22 ++ 5 files changed, 908 insertions(+), 61 deletions(-) diff --git a/vslib/vpp/SwitchVppFdb.cpp b/vslib/vpp/SwitchVppFdb.cpp index 9247fd3270..7eff007781 100644 --- a/vslib/vpp/SwitchVppFdb.cpp +++ b/vslib/vpp/SwitchVppFdb.cpp @@ -12,6 +12,328 @@ using namespace saivs; +/* ====================================================================== + * L2 classify-based punt infrastructure. + * + * Shared classify tables and resolved hit-next indices are initialised + * lazily on the first BD member add and persist for the lifetime of + * the process. + * + * Scope: + * - LLDP (0x88cc): redirect to linux-cp-punt at l2-input-classify + * so the frame ends up on the originating member's LCP host tap. + * - DHCPv4 client→server broadcast: redirect to + * sonic-ext-l2-trap-fixup, which sets VLIB_RX to the parent + * physical interface (parent of the bridged sub-if), and hands + * forwards to linux-cp-punt -> bvi-host-tap interface-output -> + * sonic-ext-aggr-tap-redirect -> member-host-tap. This emulates + * SAI_PACKET_ACTION_TRAP for DHCP on VPP-VS: the L2-flood is + * skipped entirely, so no copies are flooded to other VLAN + * members (sonic-mgmt DHCPBroadcastNotFloodedTest depends on + * this). + * + * LLDP needs the classifier to work around a VPP behavior: link-local + * multicast frames (DA in 01:80:c2:00:00:00..0F) are flooded by the BD + * because l2-input strips L2INPUT_FEAT_FWD from the per-buffer feature + * bitmap for any frame with the multicast bit set, so the FDB is never + * consulted and a static l2fib entry cannot suppress the flood. Without + * the classifier the LLDP frame would be flooded to every BD member + * (including the BVI flood-copy, which then punts via linux-cp-punt-xc + * to the BVI host tap), causing lldpd to observe the same neighbour on + * multiple netdevs and producing inconsistent neighbour discovery + * results. The l2-input-classify arc runs before the multicast + * feat-mask strip, so a redirect-punt session here consumes the frame + * and delivers it only to the originating member's LCP host tap. + * + * DHCP needs the classifier for the dual problem: a client broadcast + * (dst=ff:ff:ff:ff:ff:ff) would otherwise hit the BD's l2-flood and + * fan out to every member port. Trapping at l2-input-classify + * consumes the buffer before flood, then this node hand-off to + * sonic-ext-l2-trap-fixup delivers exactly one copy via the parent + * phys's host tap (where the kernel's 8021q layer demuxes it up to + * the Vlan netdev where dhcrelay is listening). + * + * LACP (0x8809) is intentionally NOT in the classifier: linux-cp + * registers linux-cp-punt-xc as the ethernet-input next for 0x8809 + * (and 0x88cc, 0x0806) via lcp_ethertype_enable() at startup. For + * LACP that dispatch happens in ethernet-input on the parent phy -- + * before l2-input runs and before any sub-interface dispatch -- so + * LACP never reaches the BD and never needs to be classifier-punted. + * The same is true for tagged LLDP: ethernet-input dispatches by + * outer ethertype, and 0x88cc != 0x8100, so tagged LLDP also bypasses + * the BD via the same linux-cp-punt-xc shortcut. Only untagged LLDP + * (which arrives on a parent that IS in the BD) needs the classifier. + * + * ARP is handled by the sonic_ext VPP plugin via the arp arc -> + * sonic-ext-aggr-tap-redirect on the BVI tap. See + * platform/vpp/vppbld/plugins/sonic_ext/. + * + * Slot selection in l2-input-classify (vnet/l2/l2_input_classify.c): + * VPP picks the per-interface table by *outer* ethertype at + * current_data (h0->type): + * - 0x0800 (IPv4) -> ip4_table_index + * - 0x86DD (IPv6) -> ip6_table_index + * - everything else -> other_table_index (incl. 0x8100 VLAN, 0x88CC LLDP) + * + * Consequently: + * + * Untagged member (wire ethertype is the inner protocol): + * IP4 slot <- untag_ip4_table (DHCPv4 broadcast match) + * OTHER slot <- untag_other_table (LLDP match) + * No chain between the two: DHCPv4 only ever reaches the IP4 + * slot; LLDP only ever reaches OTHER. + * + * Tagged member (wire ethertype is 0x8100 -> OTHER slot): + * IP4 slot <- ~0 + * OTHER slot <- tag_dhcp_table (DHCPv4 over .1Q match) + * | on miss + * v + * continue normal L2 path + * (LLDP is never tagged, so no tagged-LLDP table is needed.) + * + * Hit-next graph slots out of l2-input-classify, resolved once via + * vpp_add_node_next(): + * - linux-cp-punt : LLDP + untagged DHCPv4 + * - sonic-ext-l2-trap-fixup: tagged DHCPv4 only (rewrites VLIB_RX + * from bridged sub-if to parent phys before handing to linux- + * cp-punt, because a bridged sub-if has no LCP pair). + * Untagged DHCP does not need the fixup node: VLIB_RX on an untagged + * bridge member is already the parent phys, so linux-cp-punt resolves + * the right LCP pair directly. + * ====================================================================== */ + +#define SAIVS_CLASSIFY_ACTION_NONE 0 + +static bool s_l2_punt_classify_inited = false; +static uint32_t s_punt_next_index = ~0; /* linux-cp-punt (untagged + LLDP) */ +static uint32_t s_trap_fixup_next_index = ~0; /* sonic-ext-l2-trap-fixup (tagged DHCP only) */ + +/* Untagged member tables: ip4 slot holds DHCPv4 broadcast, + * other slot holds LLDP. No chain between them (different ethertype + * families select different slots in l2-input-classify). */ +static uint32_t s_untag_other_table = ~0; /* LLDP by ethertype */ +static uint32_t s_untag_ip4_table = ~0; /* DHCPv4 client broadcast */ + +/* Tagged member table: outer ethertype is 0x8100, so EVERY tagged + * frame (including IPv4-inside-VLAN) lands in the other slot. LLDP is + * never tagged, so only the DHCPv4-over-.1Q table is needed; it is + * attached directly to the tagged member's OTHER slot. */ +static uint32_t s_tag_dhcp_table = ~0; /* DHCPv4 broadcast over .1Q */ + +/* + * DHCPv4 client→server broadcast match. We match on: + * - dst MAC == ff:ff:ff:ff:ff:ff (mandatory: client-side broadcast) + * - ethertype == 0x0800 (IPv4) + * - IP protocol == 17 (UDP) + * - UDP dport == 67 (BOOTPS; sport is NOT matched) + * + * IP header length is NOT matched: virtually all DHCP packets have + * IHL=5 (no options) so the UDP header lies at the canonical offset, + * but if a future client sends DHCP with IP options the classifier + * mask would also need to chain a second table. Today this is + * a non-issue and ASIC TCAM rules used by SAI_PACKET_ACTION_TRAP + * make the same assumption. + * + * Server→client broadcast (sport=67, dport=68) is NOT matched here. + * That direction is generated by the local relay/server stack and + * exits via the BVI tap; it does not arrive on a bridge member's + * wire RX in this topology. + */ +#define SAIVS_DHCP_BOOTPC 68 +#define SAIVS_DHCP_BOOTPS 67 + +static int l2_punt_classify_init() +{ + SWSS_LOG_ENTER(); + if (s_l2_punt_classify_inited) + return 0; + + /* Resolve hit-next graph slots out of l2-input-classify. These + * register the target nodes as nexts of l2-input-classify so the + * hit_next_index in each session is a valid graph edge. + * + * linux-cp-punt is required (LLDP + untagged DHCP). + * sonic-ext-l2-trap-fixup is only required for tagged DHCP: it + * rewrites VLIB_RX from the bridged sub-if to the parent phys so + * linux-cp-punt picks the parent's LCP host tap (the sub-if has + * no LCP pair when it is a pure bridge member). */ + if (vpp_add_node_next("l2-input-classify", "linux-cp-punt", + &s_punt_next_index) != 0) { + SWSS_LOG_ERROR("l2_punt_classify_init: vpp_add_node_next(linux-cp-punt) failed"); + return -1; + } + if (vpp_add_node_next("l2-input-classify", "sonic-ext-l2-trap-fixup", + &s_trap_fixup_next_index) != 0) { + /* Not fatal: without the fixup node, only tagged-DHCP punt + * is broken. Untagged DHCP and LLDP still work via the + * direct linux-cp-punt next. */ + SWSS_LOG_WARN("l2_punt_classify_init: sonic-ext-l2-trap-fixup not registered; " + "tagged DHCP broadcast will not be punted (untagged DHCP unaffected)"); + s_trap_fixup_next_index = ~0; + } else { + SWSS_LOG_NOTICE("l2_punt_classify_init: trap_fixup_next_index=%u", + s_trap_fixup_next_index); + } + SWSS_LOG_NOTICE("l2_punt_classify_init: punt_next_index=%u", s_punt_next_index); + + /* --- Untagged IP4-slot table (DHCPv4 broadcast) --- + * + * l2-input-classify selects this slot when the outer ethertype is + * 0x0800, which is the case for ALL IPv4 frames on an untagged + * member (including DHCPv4 client broadcasts). + * + * Match span: 0..37 -> skip=0, match=3 (3 x 16 = 48-byte vector). + * bytes 0..5 -> dst MAC = ff:ff:ff:ff:ff:ff + * bytes 12..13 -> ethertype = 0x0800 + * byte 23 -> IP protocol = UDP (17) + * bytes 36..37 -> UDP dport (67); sport (bytes 34..35) NOT matched + * + * Hit_next is linux-cp-punt directly: VLIB_RX is already the + * parent phys (untagged BD member IS the parent phys; sub-if + * dispatch never ran), so linux-cp-punt picks the correct LCP + * pair straight away. No fixup node needed. + * + * No chain on miss: non-DHCP IPv4 traffic should fall through to + * the rest of the L2 feature arc unchanged. + */ + { + uint8_t mask[48] = {0}; + mask[0] = mask[1] = mask[2] = mask[3] = mask[4] = mask[5] = 0xFF; + mask[12] = 0xFF; mask[13] = 0xFF; + mask[23] = 0xFF; + mask[36] = 0xFF; mask[37] = 0xFF; /* UDP dport only (sport ignored) */ + + if (vpp_classify_table_create( + 8 /*nbuckets*/, 4*1024 /*memory_size: 1 session*/, + 0 /*skip*/, 3 /*match_n_vectors*/, + ~0 /*next_table*/, ~0 /*miss_next=continue*/, + mask, 48, &s_untag_ip4_table) != 0) { + SWSS_LOG_ERROR("l2_punt_classify_init: untag_ip4 table create failed"); + s_untag_ip4_table = ~0; + } else { + uint8_t m[48] = {0}; + m[0] = m[1] = m[2] = m[3] = m[4] = m[5] = 0xFF; + m[12] = 0x08; m[13] = 0x00; + m[23] = 0x11; /* IPPROTO_UDP */ + m[36] = (SAIVS_DHCP_BOOTPS >> 8) & 0xFF; /* UDP dport == 67 */ + m[37] = SAIVS_DHCP_BOOTPS & 0xFF; + vpp_classify_session_add(s_untag_ip4_table, s_punt_next_index, + m, 48, 0, 0, SAIVS_CLASSIFY_ACTION_NONE); + } + } + + /* --- Untagged OTHER-slot table: match ethertype at offset 12 --- + * l2-input-classify selects this slot for non-IPv4/IPv6 ethertypes + * (LLDP 0x88CC, ARP, etc.). Single LLDP session, no chain. */ + { + uint8_t mask[16] = {0}; + mask[12] = 0xFF; mask[13] = 0xFF; + + if (vpp_classify_table_create( + 8 /*nbuckets*/, 4*1024 /*memory_size: 1 session*/, + 0 /*skip*/, 1 /*match_n_vectors*/, + ~0 /*next_table=none*/, + ~0 /*miss_next=continue*/, + mask, 16, &s_untag_other_table) != 0) { + SWSS_LOG_ERROR("l2_punt_classify_init: untag_other table create failed"); + return -1; + } + + /* LLDP 0x88CC → redirect-punt (consume) */ + uint8_t m[16] = {0}; m[12] = 0x88; m[13] = 0xCC; + vpp_classify_session_add(s_untag_other_table, s_punt_next_index, + m, 16, 0, 0, SAIVS_CLASSIFY_ACTION_NONE); + } + + /* --- Tagged DHCP table. Frame at l2-input-classify on a tagged + * sub-if still carries the outer 802.1Q tag (VTR pop has not + * yet run), so all post-L2 offsets shift by +4. + * + * Match span: 0..41 -> skip=0, match=3 (48-byte vector). + * bytes 0..5 -> dst MAC = ff:ff:ff:ff:ff:ff + * bytes 16..17 -> inner ethertype = 0x0800 + * byte 27 -> IP protocol = UDP (= 14 + 4 vlan + 9) + * bytes 40..41 -> UDP dport (67); sport (bytes 38..39) NOT matched + * + * Tagged hit_next is sonic-ext-l2-trap-fixup (NOT linux-cp-punt + * directly): VLIB_RX is the sub-if (e.g. Ethernet0.10), which has + * no LCP pair for a bridged sub-if, so linux-cp-punt would drop + * the frame. The fixup node rewrites VLIB_RX to the parent phys + * (Ethernet0), which always has an LCP pair. Skip table install + * if the fixup node is not registered (untagged DHCP still works). + */ + if (s_trap_fixup_next_index != ~0u) { + uint8_t mask[48] = {0}; + mask[0] = mask[1] = mask[2] = mask[3] = mask[4] = mask[5] = 0xFF; + mask[16] = 0xFF; mask[17] = 0xFF; + mask[27] = 0xFF; + mask[40] = 0xFF; mask[41] = 0xFF; /* UDP dport only (sport ignored) */ + + if (vpp_classify_table_create( + 8, 4*1024 /*memory_size: 1 session*/, + 0 /*skip*/, 3 /*match_n_vectors*/, + ~0 /*next_table*/, ~0 /*miss_next*/, + mask, 48, &s_tag_dhcp_table) != 0) { + SWSS_LOG_ERROR("l2_punt_classify_init: tag_dhcp table create failed"); + s_tag_dhcp_table = ~0; + } else { + uint8_t m[48] = {0}; + m[0] = m[1] = m[2] = m[3] = m[4] = m[5] = 0xFF; + m[16] = 0x08; m[17] = 0x00; + m[27] = 0x11; + m[40] = (SAIVS_DHCP_BOOTPS >> 8) & 0xFF; /* UDP dport == 67 */ + m[41] = SAIVS_DHCP_BOOTPS & 0xFF; + vpp_classify_session_add(s_tag_dhcp_table, s_trap_fixup_next_index, + m, 48, 0, 0, SAIVS_CLASSIFY_ACTION_NONE); + } + } + + s_l2_punt_classify_inited = true; + SWSS_LOG_NOTICE("L2 punt classify tables initialized: " + "untag_ip4=%u untag_other=%u tag_dhcp=%u " + "punt_next=%u trap_fixup_next=%u", + s_untag_ip4_table, s_untag_other_table, + s_tag_dhcp_table, + s_punt_next_index, s_trap_fixup_next_index); + return 0; +} + +static int l2_punt_classify_apply(const char *hwif_name, bool is_tagged) +{ + SWSS_LOG_ENTER(); + if (l2_punt_classify_init() != 0) { + SWSS_LOG_ERROR("l2_punt_classify_apply: init failed for %s", hwif_name); + return -1; + } + + /* Tagged frames carry outer ethertype 0x8100 -> OTHER slot only. + * LLDP is never tagged, so the tagged OTHER slot only needs the + * DHCPv4-over-.1Q table. Untagged frames carry the inner ethertype + * on the wire, so DHCPv4 (0x0800) lands in the IP4 slot and LLDP + * (0x88CC) in the OTHER slot; install both. */ + uint32_t ip4_tbl = is_tagged ? (uint32_t)~0u : s_untag_ip4_table; + uint32_t other_tbl = is_tagged ? s_tag_dhcp_table : s_untag_other_table; + + int rc = vpp_classify_set_interface_l2_tables( + hwif_name, ip4_tbl, ~0 /*ip6*/, other_tbl, true /*is_input*/); + if (rc == 0) { + SWSS_LOG_NOTICE("l2_punt_classify_apply: %s tagged=%d ip4=%u other=%u", + hwif_name, is_tagged, ip4_tbl, other_tbl); + } else { + SWSS_LOG_ERROR("l2_punt_classify_apply: set_interface_l2_tables failed(%d) for %s", + rc, hwif_name); + } + return rc; +} + +static int l2_punt_classify_remove(const char *hwif_name) +{ + SWSS_LOG_ENTER(); + /* Detach all tables from the interface */ + return vpp_classify_set_interface_l2_tables( + hwif_name, ~0, ~0, ~0, true /*is_input*/); +} + /** * @brief FDB_ENTRY FLUSH Modes. */ @@ -190,12 +512,9 @@ sai_status_t SwitchVpp::vpp_create_vlan_member( */ snprintf(host_subifname, sizeof(host_subifname), "%s.%u", hwifname, vlan_id); - /* The host(tap) subinterface is also created as part of the vpp subinterface creation */ + /* lcp-auto-subint creates the host tap automatically */ create_sub_interface(hwifname, vlan_id, vlan_id); - /* Get new list of physical interfaces from VS */ - refresh_interfaces_list(); - hw_ifname = host_subifname; //Create bridge and set the l2 port @@ -203,8 +522,28 @@ sai_status_t SwitchVpp::vpp_create_vlan_member( swif_bdid_track(hw_ifname, bridge_id); + /* Strip the outer 802.1Q tag on ingress to the BD; VPP pushes + * it back on egress symmetrically. Required so the BD/BVI + * sees untagged frames and ip4-dvr-reinject does not deliver + * tagged frames to the LCP host tap. + */ + { + vpp_l2_vtr_op_t vtr_op = L2_VTR_POP_1; + vpp_vlan_type_t push_dot1q = VLAN_DOT1Q; + uint32_t tag1 = (uint32_t)vlan_id; + uint32_t tag2 = ~0; + set_l2_interface_vlan_tag_rewrite(hw_ifname, tag1, tag2, push_dot1q, vtr_op); + } + //Set interface state up interface_set_state(hw_ifname, true); + + /* Enable L2 classify-based punt on the sub-interface so control + * protocols (LLDP, LACP, ARP, DHCP) are punted/copied to the + * LCP host tap. Tagged frames still carry the 802.1Q header + * when l2-input-classify runs (VTR has not yet stripped it). + */ + l2_punt_classify_apply(hw_ifname, true /*tagged*/); } else if (tagging_mode == SAI_VLAN_TAGGING_MODE_UNTAGGED) { @@ -215,12 +554,16 @@ sai_status_t SwitchVpp::vpp_create_vlan_member( swif_bdid_track(hw_ifname, bridge_id); - //Set the vlan member to bridge and tags rewrite - vpp_l2_vtr_op_t vtr_op = L2_VTR_PUSH_1; - vpp_vlan_type_t push_dot1q = VLAN_DOT1Q; - uint32_t tag1 = (uint32_t)vlan_id; - uint32_t tag2 = ~0; - set_l2_interface_vlan_tag_rewrite(hw_ifname, tag1, tag2, push_dot1q, vtr_op); + // Untagged BD member: do NOT install an input tag-rewrite on + // the phy. The wire frame is untagged and must remain untagged + // through l2-input/BD/BVI; otherwise ip4-dvr-reinject will + // egress LCP traffic with a stale 802.1Q tag. + + /* Enable L2 classify-based punt on the parent so control + * protocols (LLDP, LACP, ARP, DHCP) arriving on the parent + * are punted/copied to the LCP host tap. + */ + l2_punt_classify_apply(hw_ifname, false /*untagged*/); } else { SWSS_LOG_ERROR("Tagging Mode %d not implemented", tagging_mode); @@ -367,33 +710,42 @@ sai_status_t SwitchVpp::vpp_remove_vlan_member( char host_subifname[32]; if (tagging_mode == SAI_VLAN_TAGGING_MODE_UNTAGGED) { + /* Disable L2 classify punt before removing the parent + * from the BD (mirror of the add path). + */ + l2_punt_classify_remove(hw_ifname); - //First disable tag-rewrite. - vpp_l2_vtr_op_t vtr_op =L2_VTR_DISABLED; - vpp_vlan_type_t push_dot1q = VLAN_DOT1Q; - uint32_t tag1 = (uint32_t)vlan_id; - uint32_t tag2 = ~0; - set_l2_interface_vlan_tag_rewrite(hw_ifname, tag1, tag2, push_dot1q, vtr_op); - - //Remove interface from bridge, interface type should be changed to others types like l3. + /* Untagged member: parent itself is the BD member. No VTR was + * installed on add, so just remove the parent from the BD. + */ set_sw_interface_l2_bridge(hw_ifname, bridge_id, false, VPP_API_PORT_TYPE_NORMAL); swif_bdid_untrack(hw_ifname); } else if (tagging_mode == SAI_VLAN_TAGGING_MODE_TAGGED) { - - // set interface l2 tag-rewrite GigabitEthernet0/8/0.200 disable + // Tagged member: subif . is the BD member. + const char *parent_hwif = hw_ifname; snprintf(host_subifname, sizeof(host_subifname), "%s.%u", hw_ifname, vlan_id); hw_ifname = host_subifname; + + /* Disable L2 classify punt on subif before teardown */ + l2_punt_classify_remove(hw_ifname); + + // Disable tag-rewrite before removing the subif from the bridge. + { + vpp_l2_vtr_op_t vtr_op = L2_VTR_DISABLED; + vpp_vlan_type_t push_dot1q = VLAN_DOT1Q; + uint32_t tag1 = (uint32_t)vlan_id; + uint32_t tag2 = ~0; + set_l2_interface_vlan_tag_rewrite(hw_ifname, tag1, tag2, push_dot1q, vtr_op); + } + // Remove the l2 port from bridge set_sw_interface_l2_bridge(hw_ifname, bridge_id, false, VPP_API_PORT_TYPE_NORMAL); swif_bdid_untrack(hw_ifname); - // delete subinterface - delete_sub_interface(hw_ifname, vlan_id); - - // Get new list of physical interfaces from VS - refresh_interfaces_list(); + // delete subinterface (lcp-auto-subint removes host tap automatically) + delete_sub_interface(parent_hwif, vlan_id); } else { @@ -472,16 +824,31 @@ sai_status_t SwitchVpp::vpp_create_bvi_interface( //Set interface state up interface_set_state(hw_ifname, true); - //Set the bvi as access or untagged port of the bridge - vpp_l2_vtr_op_t vtr_op = L2_VTR_PUSH_1; - vpp_vlan_type_t push_dot1q = VLAN_DOT1Q; - uint32_t tag1 = (uint32_t)vlan_id; - uint32_t tag2 = ~0; - set_l2_interface_vlan_tag_rewrite(hw_ifname, tag1, tag2, push_dot1q, vtr_op); + // BVI is the L3 endpoint of the BD and exchanges *untagged* frames + // with the BD, matching the Linux model where Vlan is presented + // untagged to the IP stack. No vlan tag-rewrite on the BVI itself. + + // Create LCP pair between bvi and a Linux tap (tap_Vlan). + // The pair is required so that: + // - lcp_itf_pair_add fires the sonic_ext plugin's vft callback, + // which enables sonic-ext-aggr-tap-redirect on the BVI tap's + // interface-output arc (used by ARP / L3 punt paths). + // - linux-cp-punt[-xc] has a host tap to set VLIB_TX to before + // aggr-tap-redirect rewrites it to the originating member tap. + // The kernel-visible Vlan netdev is provisioned independently by + // SONiC; data-plane punts land on the originating member tap via + // aggr-tap-redirect, not on tap_Vlan, so no tc mirror is required. + { + std::string vpp_ifname = std::string("bvi") + std::to_string(vlan_id); + std::string tap_name = std::string("tap_Vlan") + std::to_string(vlan_id); - //Set the arp termination for bridge - uint32_t bd_id = (uint32_t) vlan_id; - set_bridge_domain_flags(bd_id, VPP_BD_FLAG_ARP_TERM,true); + SWSS_LOG_NOTICE("configure_lcp_interface vpp_name:%s tap:%s", + vpp_ifname.c_str(), tap_name.c_str()); + configure_lcp_interface(vpp_ifname.c_str(), tap_name.c_str(), true); + + refresh_interfaces_list(); + interface_set_state(tap_name.c_str(), true); + } return SAI_STATUS_SUCCESS; } @@ -535,25 +902,25 @@ sai_status_t SwitchVpp::vpp_delete_bvi_interface( return SAI_STATUS_FAILURE; } auto vlan_id = attr.value.u16; + uint32_t bd_id = (uint32_t)vlan_id; char hw_bviifname[32]; const char *hw_ifname; snprintf(hw_bviifname, sizeof(hw_bviifname), "bvi%u",vlan_id); hw_ifname = hw_bviifname; - //Disable arp termination for bridge - uint32_t bd_id = (uint32_t) vlan_id; - set_bridge_domain_flags(bd_id, VPP_BD_FLAG_ARP_TERM, false); - - //First disable tag-rewrite. - vpp_l2_vtr_op_t vtr_op = L2_VTR_DISABLED; - vpp_vlan_type_t push_dot1q = VLAN_DOT1Q; - uint32_t tag1 = (uint32_t)vlan_id; - uint32_t tag2 = ~0; - set_l2_interface_vlan_tag_rewrite(hw_ifname, tag1, tag2, push_dot1q, vtr_op); - //Remove interface from bridge, interface type should be changed to others types like l3. set_sw_interface_l2_bridge(hw_ifname, bd_id, false, VPP_API_PORT_TYPE_BVI); + // Tear down LCP pair for the BVI tap. Tap name is deterministic: + // tap_Vlan; no per-instance lookup table is needed. + { + std::string tap_name = std::string("tap_Vlan") + std::to_string(vlan_id); + + SWSS_LOG_NOTICE("configure_lcp_interface remove vpp_name:%s tap:%s", + hw_ifname, tap_name.c_str()); + configure_lcp_interface(hw_ifname, tap_name.c_str(), false); + } + //Remove the bvi interface delete_bvi_interface(hw_ifname); diff --git a/vslib/vpp/SwitchVppHostif.cpp b/vslib/vpp/SwitchVppHostif.cpp index 8d1c52e3bc..ce18e17a60 100644 --- a/vslib/vpp/SwitchVppHostif.cpp +++ b/vslib/vpp/SwitchVppHostif.cpp @@ -357,6 +357,7 @@ sai_status_t SwitchVpp::vs_create_hostif_tap_interface( const char *hwif_name = tap_to_hwif_name(dev); configure_lcp_interface(hwif_name, dev, true); + interface_set_promiscuous(hwif_name, true); { bool link_up = false; diff --git a/vslib/vpp/SwitchVppRif.cpp b/vslib/vpp/SwitchVppRif.cpp index 2a07d209b9..af1aa7f712 100644 --- a/vslib/vpp/SwitchVppRif.cpp +++ b/vslib/vpp/SwitchVppRif.cpp @@ -1604,7 +1604,6 @@ sai_status_t SwitchVpp::vpp_create_router_interface( { snprintf(host_subifname, sizeof(host_subifname), "%s.%u", dev, vlan_id); - /* The host(tap) subinterface is also created as part of the vpp subinterface creation */ const char *parent_hwif; char hw_subif_parent[32]; if (ot == SAI_OBJECT_TYPE_LAG) { @@ -1615,6 +1614,18 @@ sai_status_t SwitchVpp::vpp_create_router_interface( } create_sub_interface(parent_hwif, vlan_id, vlan_id); + /* + * lcp-auto-subint is disabled in VPP startup config (vlan-bvi HLD §3.6), + * so the VPP sub-interface does NOT get an automatic linux-cp pair. + * Explicitly create the LCP pair binding . (VPP side) + * to the kernel sub-vlan netdev (.). Without this the + * sub-interface will not show up in `vppctl show lcp` and host punt + * will not work for the SUB_PORT RIF. + */ + char vpp_subif_name[64]; + snprintf(vpp_subif_name, sizeof(vpp_subif_name), "%s.%u", parent_hwif, vlan_id); + configure_lcp_interface(vpp_subif_name, host_subifname, true); + /* Get new list of physical interfaces from VS */ refresh_interfaces_list(); @@ -1935,17 +1946,22 @@ sai_status_t SwitchVpp::vpp_remove_router_interface(sai_object_id_t rif_id) } else { parent_hwif = tap_to_hwif_name(dev); } + + /* + * Tear down the explicit LCP pair created in vpp_create_router_interface for + * SUB_PORT (lcp-auto-subint is disabled, HLD §3.6). hostif name is ignored by + * the LCP plugin on delete, but pass the symmetric value for log clarity. + */ + char vpp_subif_name[64]; + char host_subifname[64]; + snprintf(vpp_subif_name, sizeof(vpp_subif_name), "%s.%u", parent_hwif, vlan_id); + snprintf(host_subifname, sizeof(host_subifname), "%s.%u", dev, vlan_id); + configure_lcp_interface(vpp_subif_name, host_subifname, false); + delete_sub_interface(parent_hwif, vlan_id); /* Get new list of physical interfaces from VS */ refresh_interfaces_list(); -/* - char host_subifname[32], hwif_name[32]; - snprintf(host_subifname, sizeof(host_subifname), "%s.%u", dev, vlan_id); - snprintf(hwif_name, sizeof(hwif_name), "%s.%u", tap_to_hwif_name(dev), vlan_id); - configure_lcp_interface(tap_to_hwif_name(dev), host_subifname); -*/ - return SAI_STATUS_SUCCESS; } diff --git a/vslib/vpp/vppxlate/SaiVppXlate.c b/vslib/vpp/vppxlate/SaiVppXlate.c index 174d8ac000..60644ecbd4 100644 --- a/vslib/vpp/vppxlate/SaiVppXlate.c +++ b/vslib/vpp/vppxlate/SaiVppXlate.c @@ -77,6 +77,12 @@ #include #include +#include +#include + +#include +#include + /* l2 API inclusion */ #define vl_typedefs @@ -100,6 +106,48 @@ #include #undef vl_api_version +/* classify API inclusion */ + +#define vl_typedefs +#include +#undef vl_typedefs + +#define vl_endianfun +#include +#undef vl_endianfun + +#define vl_calcsizefun +#include +#undef vl_calcsizefun + +#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__) +#define vl_printfun +#include +#undef vl_printfun + +#define vl_api_version(n, v) static u32 classify_api_version = v; +#include +#undef vl_api_version + +/* vlib API inclusion (for get_next_index) */ + +#define vl_endianfun +#include +#undef vl_endianfun + +#define vl_calcsizefun +#include +#undef vl_calcsizefun + +#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__) +#define vl_printfun +#include +#undef vl_printfun + +#define vl_api_version(n, v) static u32 vlibapi_version = v; +#include +#undef vl_api_version + /* tunterm API inclusion */ #define vl_typedefs @@ -407,15 +455,39 @@ do { \ * events causing vl_socket_client_read to return before the expected result is received. If * vam->result_ready is not set, which should be set when API callback function is called, then * it means we get some unsolicited events and we need to retry. + * + * The reply message queue can be saturated when there are lots of events. Use a dynamic timeout: + * - Hard cap: 10 seconds total wait time. Tolerates large bursts of unsolicited events that + * delay processing of the actual reply. + * - Idle cap: 1 second since the last successfully processed message. Each time a message is + * processed (even an unsolicited one) the idle deadline is extended by 1 second, up to the + * hard cap. + * - vl_socket_client_read is called with a 1 second wait so it returns frequently while + * messages are being drained, allowing the WR loop to refresh the idle deadline and re-check + * result_ready promptly. + * - The loop breaks when we get the expected reply (vam->result_ready == 1), the 10 second hard + * cap is reached, or 1 second elapses with no new message processed. */ #define WR(ret) \ do { \ - f64 timeout = vat_time_now (vam) + 1.0; \ + f64 start_time = vat_time_now (vam); \ + f64 hard_deadline = start_time + 10.0; \ + f64 idle_deadline = start_time + 1.0; \ socket_client_main_t *scm = vam->socket_client_main; \ + int _wr_rv; \ ret = -99; \ - while (vat_time_now (vam) < timeout) { \ - if (scm && scm->socket_enable) \ - vl_socket_client_read (5); \ + while (1) { \ + f64 now = vat_time_now (vam); \ + if (now >= hard_deadline || now >= idle_deadline) \ + break; \ + if (scm && scm->socket_enable) { \ + _wr_rv = vl_socket_client_read (1); \ + if (_wr_rv == 0) { \ + idle_deadline = vat_time_now (vam) + 1.0; \ + if (idle_deadline > hard_deadline) \ + idle_deadline = hard_deadline; \ + } \ + } \ if (vam->result_ready == 1) { \ ret = vam->retval; \ break; \ @@ -837,6 +909,14 @@ vl_api_create_subif_reply_t_handler (vl_api_create_subif_reply_t *msg) { int retval = (int)ntohl((uint32_t)msg->retval); set_reply_status(retval); + + if (msg->context) { + u32 *swif_idx = (u32 *) get_index_ptr(msg->context); + if (swif_idx) { + *swif_idx = ntohl(msg->sw_if_index); + } + release_index(msg->context); + } } static void @@ -884,6 +964,13 @@ vl_api_sw_interface_set_flags_reply_t_handler (vl_api_sw_interface_set_flags_rep set_reply_status(retval); } +static void +vl_api_sw_interface_set_promisc_reply_t_handler (vl_api_sw_interface_set_promisc_reply_t *msg) +{ + int retval = (int)ntohl((uint32_t)msg->retval); + set_reply_status(retval); +} + static void vl_api_sw_interface_set_mtu_reply_t_handler (vl_api_sw_interface_set_mtu_reply_t *msg) { @@ -1358,6 +1445,59 @@ vl_api_sr_set_encap_source_reply_t_handler(vl_api_sr_set_encap_source_reply_t *m set_reply_status(retval); } +/* classify API reply handlers */ + +static void vl_api_classify_add_del_table_reply_t_handler( + vl_api_classify_add_del_table_reply_t *msg) +{ + int retval = (int)ntohl((uint32_t)msg->retval); + set_reply_status(retval); + + uint32_t *table_index = (uint32_t *) get_index_ptr(msg->context); + *table_index = ntohl(msg->new_table_index); + release_index(msg->context); +} + +static void vl_api_classify_add_del_session_reply_t_handler( + vl_api_classify_add_del_session_reply_t *msg) +{ + int retval = (int)ntohl((uint32_t)msg->retval); + set_reply_status(retval); +} + +static void vl_api_classify_set_interface_l2_tables_reply_t_handler( + vl_api_classify_set_interface_l2_tables_reply_t *msg) +{ + int retval = (int)ntohl((uint32_t)msg->retval); + set_reply_status(retval); +} + +/* vlib API reply handler (get_next_index) */ + +static void vl_api_get_next_index_reply_t_handler( + vl_api_get_next_index_reply_t *msg) +{ + int retval = (int)ntohl((uint32_t)msg->retval); + set_reply_status(retval); + + uint32_t *next_index = (uint32_t *) get_index_ptr(msg->context); + *next_index = ntohl(msg->next_index); + release_index(msg->context); +} + +/* vlib API reply handler (add_node_next) */ + +static void vl_api_add_node_next_reply_t_handler( + vl_api_add_node_next_reply_t *msg) +{ + int retval = (int)ntohl((uint32_t)msg->retval); + set_reply_status(retval); + + uint32_t *next_index = (uint32_t *) get_index_ptr(msg->context); + *next_index = ntohl(msg->next_index); + release_index(msg->context); +} + #define vl_api_get_first_msg_id_reply_t_handler vl_noop_handler #define vl_api_get_first_msg_id_reply_t_handler_json vl_noop_handler @@ -1373,6 +1513,8 @@ static u16 tunterm_msg_id_base; static u16 bfd_msg_id_base; static u16 sr_msg_id_base; static u16 bond_msg_id_base; +static u16 classify_msg_id_base; +static u16 vlib_msg_id_base; static void vpp_base_vpe_init(void) { @@ -1409,6 +1551,11 @@ static void vpp_base_vpe_init(void) #define BFD_MSG_ID(id) \ (VL_API_##id + bfd_msg_id_base) +#define CLASSIFY_MSG_ID(id) \ + (VL_API_##id + classify_msg_id_base) + +#define VLIB_API_MSG_ID(id) \ + (VL_API_##id + vlib_msg_id_base) #define SFLOW_MSG_ID(id) \ (VL_API_##id + sflow_msg_id_base) @@ -1422,6 +1569,7 @@ static void vpp_base_vpe_init(void) _(INTERFACE_MSG_ID(SW_INTERFACE_GET_TABLE_REPLY), sw_interface_get_table_reply) \ _(INTERFACE_MSG_ID(SW_INTERFACE_ADD_DEL_ADDRESS_REPLY), sw_interface_add_del_address_reply) \ _(INTERFACE_MSG_ID(SW_INTERFACE_SET_FLAGS_REPLY), sw_interface_set_flags_reply) \ + _(INTERFACE_MSG_ID(SW_INTERFACE_SET_PROMISC_REPLY), sw_interface_set_promisc_reply) \ _(INTERFACE_MSG_ID(SW_INTERFACE_SET_MTU_REPLY), sw_interface_set_mtu_reply) \ _(INTERFACE_MSG_ID(SW_INTERFACE_SET_MAC_ADDRESS_REPLY), sw_interface_set_mac_address_reply) \ _(INTERFACE_MSG_ID(SW_INTERFACE_SET_UNNUMBERED_REPLY), sw_interface_set_unnumbered_reply) \ @@ -1458,6 +1606,11 @@ static void vpp_base_vpe_init(void) _(BFD_MSG_ID(WANT_BFD_EVENTS_REPLY), want_bfd_events_reply) \ _(BFD_MSG_ID(BFD_UDP_ENABLE_MULTIHOP_REPLY), bfd_udp_enable_multihop_reply) \ _(BFD_MSG_ID(BFD_UDP_SET_TOS_REPLY), bfd_udp_set_tos_reply) \ + _(CLASSIFY_MSG_ID(CLASSIFY_ADD_DEL_TABLE_REPLY), classify_add_del_table_reply) \ + _(CLASSIFY_MSG_ID(CLASSIFY_ADD_DEL_SESSION_REPLY), classify_add_del_session_reply) \ + _(CLASSIFY_MSG_ID(CLASSIFY_SET_INTERFACE_L2_TABLES_REPLY), classify_set_interface_l2_tables_reply) \ + _(VLIB_API_MSG_ID(GET_NEXT_INDEX_REPLY), get_next_index_reply) \ + _(VLIB_API_MSG_ID(ADD_NODE_NEXT_REPLY), add_node_next_reply) static u16 ip_msg_id_base, ip_nbr_msg_id_base, lcp_msg_id_base; @@ -1638,6 +1791,14 @@ static void get_base_msg_id() tunterm_msg_id_base = vl_client_get_first_plugin_msg_id ((char *) msg_base_lookup_name); assert(tunterm_msg_id_base != (u16) ~0); + msg_base_lookup_name = format (0, "classify_%08x%c", classify_api_version, 0); + classify_msg_id_base = vl_client_get_first_plugin_msg_id ((char *) msg_base_lookup_name); + assert(classify_msg_id_base != (u16) ~0); + + msg_base_lookup_name = format (0, "vlib_%08x%c", vlibapi_version, 0); + vlib_msg_id_base = vl_client_get_first_plugin_msg_id ((char *) msg_base_lookup_name); + assert(vlib_msg_id_base != (u16) ~0); + msg_base_lookup_name = format (0, "sflow_%08x%c", sflow_api_version, 0); sflow_msg_id_base = vl_client_get_first_plugin_msg_id ((char *) msg_base_lookup_name); assert(sflow_msg_id_base != (u16) ~0); @@ -1896,7 +2057,7 @@ static int __delete_loopback (vat_main_t *vam, const char *hwif_name, u32 instan return ret; } -static int __create_sub_interface (vat_main_t *vam, vl_api_interface_index_t if_idx, u32 sub_id, u16 vlan_id) +static int __create_sub_interface (vat_main_t *vam, vl_api_interface_index_t if_idx, u32 sub_id, u16 vlan_id, u32 *new_sw_if_index) { vl_api_create_subif_t *mp; int ret; @@ -1912,6 +2073,12 @@ static int __create_sub_interface (vat_main_t *vam, vl_api_interface_index_t if_ /* create_sub_interfaces() from vnet/interface_cli.c */ mp->sub_if_flags = htonl(SUB_IF_API_FLAG_EXACT_MATCH | SUB_IF_API_FLAG_ONE_TAG); + + if (new_sw_if_index) { + *new_sw_if_index = (u32) ~0; + mp->context = store_ptr(new_sw_if_index); + } + S (mp); WR (ret); @@ -1994,7 +2161,7 @@ int init_vpp_client() vpp_acl_counters_enable_disable(true); - /* Enable LACP punt/xc in linux-cp */ + /* Enable LACP punt/xc in linux-cp (no flood) */ vpp_lcp_ethertype_enable(0x8809); /* Enable LLDP in linux-cp */ vpp_lcp_ethertype_enable(0x88cc); @@ -2062,24 +2229,59 @@ int delete_loopback (const char *hwif_name, u32 instance) int create_sub_interface (const char *hwif_name, u32 sub_id, u16 vlan_id) { u32 idx; + u32 new_sw_if_index = (u32) ~0; + int rc; vat_main_t *vam = &vat_main; idx = get_swif_idx(vam, hwif_name); SAIVPP_INFO("swif index of interface %s is %u\n", hwif_name, idx); - return __create_sub_interface(vam, idx, sub_id, vlan_id); + rc = __create_sub_interface(vam, idx, sub_id, vlan_id, &new_sw_if_index); + + /* Insert the new sub-interface into the local sw_if_index cache so + * that subsequent get_swif_idx() lookups (e.g. configure_lcp_interface) + * resolve without a full sw_interface_dump. */ + if (rc == 0 && new_sw_if_index != (u32) ~0) { + char subif_name[64]; + u8 *s; + + snprintf(subif_name, sizeof(subif_name), "%s.%u", hwif_name, sub_id); + s = format(0, "%s%c", subif_name, 0); + hash_set_mem(vam->sw_if_index_by_interface_name, s, new_sw_if_index); + hash_set(interface_name_by_sw_index, new_sw_if_index, s); + } + + return rc; } int delete_sub_interface (const char *hwif_name, u32 sub_id) { u32 idx; + int rc; vat_main_t *vam = &vat_main; char tmpbuf[64]; snprintf(tmpbuf, sizeof(tmpbuf), "%s.%u", hwif_name, sub_id); idx = get_swif_idx(vam, tmpbuf); SAIVPP_INFO("swif index of interface %s is %u\n", tmpbuf, idx); - return __delete_sub_interface(vam, idx); + rc = __delete_sub_interface(vam, idx); + + /* Evict from local cache on success. */ + if (rc == 0 && idx != (u32) ~0) { + hash_pair_t *p; + u8 *key_to_free = NULL; + hash_foreach_pair (p, vam->sw_if_index_by_interface_name, ({ + if (strcmp((char *) p->key, tmpbuf) == 0) { + key_to_free = (u8 *) p->key; + } + })); + if (key_to_free) { + hash_unset_mem(vam->sw_if_index_by_interface_name, key_to_free); + vec_free(key_to_free); + } + hash_unset(interface_name_by_sw_index, idx); + } + return rc; } static int __set_interface_vrf (vat_main_t *vam, vl_api_interface_index_t if_idx, @@ -3071,6 +3273,46 @@ int interface_set_state (const char *hwif_name, bool is_up) return ret; } +int interface_set_promiscuous (const char *hwif_name, bool enable) +{ + vat_main_t *vam = &vat_main; + vl_api_sw_interface_set_promisc_t *mp; + int ret; + + VPP_LOCK(); + + __plugin_msg_base = interface_msg_id_base; + + M (SW_INTERFACE_SET_PROMISC, mp); + if (hwif_name) { + u32 idx; + + idx = get_swif_idx(vam, hwif_name); + if (idx != (u32) -1) { + mp->sw_if_index = htonl(idx); + } else { + SAIVPP_ERROR("Unable to get sw_index for %s\n", hwif_name); + VPP_UNLOCK(); + return -EINVAL; + } + } else { + VPP_UNLOCK(); + return -EINVAL; + } + mp->promisc_on = enable; + + S (mp); + + WR (ret); + + if (ret) { SAIVPP_ERROR("%s failed(%d) %s enable %d", __func__, ret, hwif_name, enable); } + else { SAIVPP_INFO("%s %s enable %d", __func__, hwif_name, enable); } + + VPP_UNLOCK(); + + return ret; +} + int interface_get_state (const char *hwif_name, bool *link_is_up) { vat_main_t *vam = &vat_main; @@ -4124,6 +4366,205 @@ static int vpp_lcp_ethertype_enable(u16 ethertype) return ret; } +/* ======================================================================== + * VPP Classify API wrappers for L2 punt via l2-input-classify + * ======================================================================== */ + +int vpp_classify_table_create(uint32_t nbuckets, uint32_t memory_size, + uint32_t skip_n_vectors, uint32_t match_n_vectors, + uint32_t next_table_index, uint32_t miss_next_index, + const uint8_t *mask, uint32_t mask_len, + uint32_t *new_table_index) +{ + vat_main_t *vam = &vat_main; + vl_api_classify_add_del_table_t *mp; + int ret; + + VPP_LOCK(); + + __plugin_msg_base = classify_msg_id_base; + + M22 (CLASSIFY_ADD_DEL_TABLE, mp, mask_len); + mp->is_add = true; + mp->del_chain = false; + mp->table_index = htonl(~0u); /* create new */ + mp->nbuckets = htonl(nbuckets); + mp->memory_size = htonl(memory_size); + mp->skip_n_vectors = htonl(skip_n_vectors); + mp->match_n_vectors = htonl(match_n_vectors); + mp->next_table_index = htonl(next_table_index); + mp->miss_next_index = htonl(miss_next_index); + mp->current_data_flag = 0; + mp->current_data_offset = 0; + mp->mask_len = htonl(mask_len); + clib_memcpy(mp->mask, mask, mask_len); + mp->context = store_ptr(new_table_index); + + S (mp); + WR (ret); + + if (ret) { SAIVPP_ERROR("%s failed(%d)", __func__, ret); } + else { SAIVPP_INFO("%s table_index=%u", __func__, *new_table_index); } + + VPP_UNLOCK(); + return ret; +} + +int vpp_classify_table_delete(uint32_t table_index) +{ + vat_main_t *vam = &vat_main; + vl_api_classify_add_del_table_t *mp; + int ret; + uint32_t dummy_idx = ~0u; + + VPP_LOCK(); + + __plugin_msg_base = classify_msg_id_base; + + M (CLASSIFY_ADD_DEL_TABLE, mp); + mp->is_add = false; + mp->del_chain = true; + mp->table_index = htonl(table_index); + mp->mask_len = 0; + mp->context = store_ptr(&dummy_idx); + + S (mp); + WR (ret); + + if (ret) { SAIVPP_ERROR("%s failed(%d) table %u", __func__, ret, table_index); } + else { SAIVPP_INFO("%s table %u", __func__, table_index); } + + VPP_UNLOCK(); + return ret; +} + +int vpp_classify_session_add(uint32_t table_index, uint32_t hit_next_index, + const uint8_t *match, uint32_t match_len, + uint32_t opaque_index, int32_t advance, + uint8_t action) +{ + vat_main_t *vam = &vat_main; + vl_api_classify_add_del_session_t *mp; + int ret; + + VPP_LOCK(); + + __plugin_msg_base = classify_msg_id_base; + + M22 (CLASSIFY_ADD_DEL_SESSION, mp, match_len); + mp->is_add = true; + mp->table_index = htonl(table_index); + mp->hit_next_index = htonl(hit_next_index); + mp->opaque_index = htonl(opaque_index); + mp->advance = (i32)htonl((u32)advance); + mp->action = action; + mp->metadata = 0; + mp->match_len = htonl(match_len); + clib_memcpy(mp->match, match, match_len); + + S (mp); + WR (ret); + + if (ret) { SAIVPP_ERROR("%s failed(%d) table %u", __func__, ret, table_index); } + else { SAIVPP_INFO("%s table %u hit_next %u", __func__, table_index, hit_next_index); } + + VPP_UNLOCK(); + return ret; +} + +int vpp_classify_session_del(uint32_t table_index, + const uint8_t *match, uint32_t match_len) +{ + vat_main_t *vam = &vat_main; + vl_api_classify_add_del_session_t *mp; + int ret; + + VPP_LOCK(); + + __plugin_msg_base = classify_msg_id_base; + + M22 (CLASSIFY_ADD_DEL_SESSION, mp, match_len); + mp->is_add = false; + mp->table_index = htonl(table_index); + mp->hit_next_index = 0; + mp->match_len = htonl(match_len); + clib_memcpy(mp->match, match, match_len); + + S (mp); + WR (ret); + + if (ret) { SAIVPP_ERROR("%s failed(%d) table %u", __func__, ret, table_index); } + else { SAIVPP_INFO("%s table %u", __func__, table_index); } + + VPP_UNLOCK(); + return ret; +} + +int vpp_classify_set_interface_l2_tables(const char *hwif_name, + uint32_t ip4_table_index, + uint32_t ip6_table_index, + uint32_t other_table_index, + bool is_input) +{ + vat_main_t *vam = &vat_main; + vl_api_classify_set_interface_l2_tables_t *mp; + int ret; + u32 sw_if_index; + + sw_if_index = get_swif_idx(vam, hwif_name); + if (sw_if_index == (u32) -1) { + SAIVPP_ERROR("%s: hwif %s not found", __func__, hwif_name ? hwif_name : ""); + return -1; + } + + VPP_LOCK(); + + __plugin_msg_base = classify_msg_id_base; + + M (CLASSIFY_SET_INTERFACE_L2_TABLES, mp); + mp->sw_if_index = htonl(sw_if_index); + mp->ip4_table_index = htonl(ip4_table_index); + mp->ip6_table_index = htonl(ip6_table_index); + mp->other_table_index = htonl(other_table_index); + mp->is_input = is_input ? 1 : 0; + + S (mp); + WR (ret); + + if (ret) { SAIVPP_ERROR("%s failed(%d) hwif %s", __func__, ret, hwif_name); } + else { SAIVPP_INFO("%s hwif %s ip4=%u ip6=%u other=%u", __func__, + hwif_name, ip4_table_index, ip6_table_index, other_table_index); } + + VPP_UNLOCK(); + return ret; +} + +int vpp_add_node_next(const char *node_name, const char *next_name, + uint32_t *next_index) +{ + vat_main_t *vam = &vat_main; + vl_api_add_node_next_t *mp; + int ret; + + VPP_LOCK(); + + __plugin_msg_base = vlib_msg_id_base; + + M (ADD_NODE_NEXT, mp); + strncpy((char *)mp->node_name, node_name, sizeof(mp->node_name) - 1); + strncpy((char *)mp->next_name, next_name, sizeof(mp->next_name) - 1); + mp->context = store_ptr(next_index); + + S (mp); + WR (ret); + + if (ret) { SAIVPP_ERROR("%s failed(%d) node %s next %s", __func__, ret, node_name, next_name); } + else { SAIVPP_INFO("%s node %s next %s -> %u", __func__, node_name, next_name, *next_index); } + + VPP_UNLOCK(); + return ret; +} + int create_bond_interface(uint32_t bond_id, uint32_t mode, uint32_t lb, uint32_t *swif_idx) { vat_main_t *vam = &vat_main; diff --git a/vslib/vpp/vppxlate/SaiVppXlate.h b/vslib/vpp/vppxlate/SaiVppXlate.h index 352bfc1534..ecf98cad21 100644 --- a/vslib/vpp/vppxlate/SaiVppXlate.h +++ b/vslib/vpp/vppxlate/SaiVppXlate.h @@ -297,6 +297,7 @@ typedef enum { extern int interface_ip_address_add_del(const char *hw_ifname, vpp_ip_route_t *prefix, bool is_add); extern int interface_ip_address_del_all(const char *hwif_name); extern int interface_set_state (const char *hwif_name, bool is_up); + extern int interface_set_promiscuous (const char *hwif_name, bool enable); extern int hw_interface_set_mtu(const char *hwif_name, uint32_t mtu); extern int sw_interface_set_mtu(const char *hwif_name, uint32_t mtu); extern int sw_interface_set_mac(const char *hwif_name, uint8_t *mac_address); @@ -398,6 +399,27 @@ typedef enum { extern int vpp_sw_interface_find_by_ip(vpp_ip_addr_t *search_ip, uint32_t vrf_id, uint32_t *out_sw_if_index); + + /* VPP Classify API for L2 punt */ + extern int vpp_classify_table_create(uint32_t nbuckets, uint32_t memory_size, + uint32_t skip_n_vectors, uint32_t match_n_vectors, + uint32_t next_table_index, uint32_t miss_next_index, + const uint8_t *mask, uint32_t mask_len, + uint32_t *new_table_index); + extern int vpp_classify_table_delete(uint32_t table_index); + extern int vpp_classify_session_add(uint32_t table_index, uint32_t hit_next_index, + const uint8_t *match, uint32_t match_len, + uint32_t opaque_index, int32_t advance, + uint8_t action); + extern int vpp_classify_session_del(uint32_t table_index, + const uint8_t *match, uint32_t match_len); + extern int vpp_classify_set_interface_l2_tables(const char *hwif_name, + uint32_t ip4_table_index, + uint32_t ip6_table_index, + uint32_t other_table_index, + bool is_input); + extern int vpp_add_node_next(const char *node_name, const char *next_name, + uint32_t *next_index); #ifdef __cplusplus } #endif From f9cb68e379dce0cbf2811e29aa5166b03332efc8 Mon Sep 17 00:00:00 2001 From: dypet Date: Wed, 8 Jul 2026 14:11:52 -0400 Subject: [PATCH 2/3] Address PR 1959 review comments. Signed-off-by: dypet --- vslib/vpp/SwitchVppFdb.cpp | 16 ++++++++++++++-- vslib/vpp/vppxlate/SaiVppXlate.c | 30 +++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/vslib/vpp/SwitchVppFdb.cpp b/vslib/vpp/SwitchVppFdb.cpp index 7eff007781..d2ea68572d 100644 --- a/vslib/vpp/SwitchVppFdb.cpp +++ b/vslib/vpp/SwitchVppFdb.cpp @@ -142,6 +142,7 @@ static uint32_t s_tag_dhcp_table = ~0; /* DHCPv4 broadcast over .1Q */ #define SAIVS_DHCP_BOOTPC 68 #define SAIVS_DHCP_BOOTPS 67 +/* Called only from the single SAI processing thread; the s_* globals need no locking. */ static int l2_punt_classify_init() { SWSS_LOG_ENTER(); @@ -542,8 +543,14 @@ sai_status_t SwitchVpp::vpp_create_vlan_member( * protocols (LLDP, LACP, ARP, DHCP) are punted/copied to the * LCP host tap. Tagged frames still carry the 802.1Q header * when l2-input-classify runs (VTR has not yet stripped it). + * + * Treated as best-effort: on failure the member still comes + * up, but control-plane punt on this member is not enabled. */ - l2_punt_classify_apply(hw_ifname, true /*tagged*/); + if (l2_punt_classify_apply(hw_ifname, true /*tagged*/) != 0) { + SWSS_LOG_WARN("l2_punt_classify_apply failed for tagged member %s (vlan %u)", + hw_ifname, vlan_id); + } } else if (tagging_mode == SAI_VLAN_TAGGING_MODE_UNTAGGED) { @@ -562,8 +569,13 @@ sai_status_t SwitchVpp::vpp_create_vlan_member( /* Enable L2 classify-based punt on the parent so control * protocols (LLDP, LACP, ARP, DHCP) arriving on the parent * are punted/copied to the LCP host tap. + * + * Treated as best-effort; see the tagged branch above. */ - l2_punt_classify_apply(hw_ifname, false /*untagged*/); + if (l2_punt_classify_apply(hw_ifname, false /*untagged*/) != 0) { + SWSS_LOG_WARN("l2_punt_classify_apply failed for untagged member %s (vlan %u)", + hw_ifname, vlan_id); + } } else { SWSS_LOG_ERROR("Tagging Mode %d not implemented", tagging_mode); diff --git a/vslib/vpp/vppxlate/SaiVppXlate.c b/vslib/vpp/vppxlate/SaiVppXlate.c index 60644ecbd4..484eb7ec52 100644 --- a/vslib/vpp/vppxlate/SaiVppXlate.c +++ b/vslib/vpp/vppxlate/SaiVppXlate.c @@ -1453,9 +1453,13 @@ static void vl_api_classify_add_del_table_reply_t_handler( int retval = (int)ntohl((uint32_t)msg->retval); set_reply_status(retval); - uint32_t *table_index = (uint32_t *) get_index_ptr(msg->context); - *table_index = ntohl(msg->new_table_index); - release_index(msg->context); + if (msg->context) { + uint32_t *table_index = (uint32_t *) get_index_ptr(msg->context); + if (table_index) { + *table_index = ntohl(msg->new_table_index); + } + release_index(msg->context); + } } static void vl_api_classify_add_del_session_reply_t_handler( @@ -1480,9 +1484,13 @@ static void vl_api_get_next_index_reply_t_handler( int retval = (int)ntohl((uint32_t)msg->retval); set_reply_status(retval); - uint32_t *next_index = (uint32_t *) get_index_ptr(msg->context); - *next_index = ntohl(msg->next_index); - release_index(msg->context); + if (msg->context) { + uint32_t *next_index = (uint32_t *) get_index_ptr(msg->context); + if (next_index) { + *next_index = ntohl(msg->next_index); + } + release_index(msg->context); + } } /* vlib API reply handler (add_node_next) */ @@ -1493,9 +1501,13 @@ static void vl_api_add_node_next_reply_t_handler( int retval = (int)ntohl((uint32_t)msg->retval); set_reply_status(retval); - uint32_t *next_index = (uint32_t *) get_index_ptr(msg->context); - *next_index = ntohl(msg->next_index); - release_index(msg->context); + if (msg->context) { + uint32_t *next_index = (uint32_t *) get_index_ptr(msg->context); + if (next_index) { + *next_index = ntohl(msg->next_index); + } + release_index(msg->context); + } } #define vl_api_get_first_msg_id_reply_t_handler vl_noop_handler From 8e37a2127a3e9d0a7ac6f10f3bcbda71ec3b98d7 Mon Sep 17 00:00:00 2001 From: dypet Date: Thu, 9 Jul 2026 11:02:17 -0400 Subject: [PATCH 3/3] Fix whitespace. Signed-off-by: dypet --- vslib/vpp/vppxlate/SaiVppXlate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vslib/vpp/vppxlate/SaiVppXlate.c b/vslib/vpp/vppxlate/SaiVppXlate.c index b81974d783..a868050f21 100644 --- a/vslib/vpp/vppxlate/SaiVppXlate.c +++ b/vslib/vpp/vppxlate/SaiVppXlate.c @@ -1814,7 +1814,7 @@ static void get_base_msg_id() msg_base_lookup_name = format (0, "vlib_%08x%c", vlibapi_version, 0); vlib_msg_id_base = vl_client_get_first_plugin_msg_id ((char *) msg_base_lookup_name); assert(vlib_msg_id_base != (u16) ~0); - + msg_base_lookup_name = format (0, "sflow_%08x%c", sflow_api_version, 0); sflow_msg_id_base = vl_client_get_first_plugin_msg_id ((char *) msg_base_lookup_name); assert(sflow_msg_id_base != (u16) ~0);