@@ -351,6 +351,7 @@ pub(crate) fn add_group_external(
351351 scoped_underlay_id. id ( ) ,
352352 nat_target,
353353 group_info. sources . as_deref ( ) ,
354+ group_info. external_forwarding . vlan_id ,
354355 ) ;
355356
356357 // Configure external tables and handle VLAN propagation
@@ -656,8 +657,12 @@ pub(crate) fn modify_group_external(
656657
657658 // Create rollback context for external group update
658659 let group_entry_for_rollback = group_entry. clone ( ) ;
659- let rollback_ctx =
660- GroupUpdateRollbackContext :: new ( s, group_ip, & group_entry_for_rollback) ;
660+ let rollback_ctx = GroupUpdateRollbackContext :: new (
661+ s,
662+ group_ip,
663+ & group_entry_for_rollback,
664+ new_group_info. external_forwarding . vlan_id ,
665+ ) ;
661666
662667 // Pre-compute normalized sources for rollback purposes
663668 let normalized_sources = normalize_sources ( new_group_info. sources . clone ( ) ) ;
@@ -693,10 +698,9 @@ pub(crate) fn modify_group_external(
693698 updated_group. int_fwding . nat_target = Some ( nat_target) ;
694699
695700 let old_vlan_id = updated_group. ext_fwding . vlan_id ;
696- updated_group. ext_fwding . vlan_id = new_group_info
697- . external_forwarding
698- . vlan_id
699- . or ( updated_group. ext_fwding . vlan_id ) ;
701+ // VLAN is assigned directly -> Some(x) sets VLAN, None removes VLAN
702+ updated_group. ext_fwding . vlan_id =
703+ new_group_info. external_forwarding . vlan_id ;
700704 updated_group. sources = normalize_sources (
701705 new_group_info. sources . clone ( ) . or ( updated_group. sources ) ,
702706 ) ;
@@ -789,9 +793,11 @@ pub(crate) fn modify_group_internal(
789793 s,
790794 group_ip. into ( ) ,
791795 & group_entry_for_rollback,
796+ // Internal groups don't have VLANs, so `attempted_vlan_id` is None.
797+ None ,
792798 ) ;
793799
794- // Internal groups don't update sources - always `None`
800+ // Internal groups don't update sources, so always `None`
795801 debug_assert ! (
796802 group_entry. sources. is_none( ) ,
797803 "Internal groups should not have sources"
@@ -1182,7 +1188,7 @@ pub(super) fn sources_contain_any(sources: &[IpSrc]) -> bool {
11821188/// This ensures uniqueness across the group's lifecycle and prevents
11831189/// tag collision when group IPs are reused after deletion.
11841190fn generate_default_tag ( group_ip : IpAddr ) -> String {
1185- format ! ( "{}:{}" , Uuid :: new_v4( ) , group_ip )
1191+ format ! ( "{}:{group_ip }" , Uuid :: new_v4( ) )
11861192}
11871193
11881194/// Multiple representations map to "allow any source" in P4:
@@ -1357,12 +1363,17 @@ fn configure_external_tables(
13571363 add_source_filters ( s, group_ip, group_info. sources . as_deref ( ) ) ?;
13581364
13591365 // Add NAT entry
1366+ let vlan_id = group_info. external_forwarding . vlan_id ;
13601367 match group_ip {
13611368 IpAddr :: V4 ( ipv4) => {
1362- table:: mcast:: mcast_nat:: add_ipv4_entry ( s, ipv4, nat_target) ?;
1369+ table:: mcast:: mcast_nat:: add_ipv4_entry (
1370+ s, ipv4, nat_target, vlan_id,
1371+ ) ?;
13631372 }
13641373 IpAddr :: V6 ( ipv6) => {
1365- table:: mcast:: mcast_nat:: add_ipv6_entry ( s, ipv6, nat_target) ?;
1374+ table:: mcast:: mcast_nat:: add_ipv6_entry (
1375+ s, ipv6, nat_target, vlan_id,
1376+ ) ?;
13661377 }
13671378 }
13681379
@@ -1688,39 +1699,46 @@ fn update_external_tables(
16881699 add_source_filters ( s, group_ip, new_sources. as_deref ( ) ) ?;
16891700 }
16901701
1702+ let old_vlan_id = group_entry. ext_fwding . vlan_id ;
1703+ let new_vlan_id = new_group_info. external_forwarding . vlan_id ;
1704+
16911705 // Update NAT target - external groups always have NAT targets
1692- if Some ( new_group_info. internal_forwarding . nat_target . ok_or_else ( || {
1693- DpdError :: Invalid ( "external groups must have NAT target" . to_string ( ) )
1694- } ) ?) != group_entry. int_fwding . nat_target
1706+ // Also handles VLAN changes since VLAN is part of the NAT match key
1707+ let new_nat_target =
1708+ new_group_info. internal_forwarding . nat_target . ok_or_else ( || {
1709+ DpdError :: Invalid (
1710+ "external groups must have NAT target" . to_string ( ) ,
1711+ )
1712+ } ) ?;
1713+
1714+ if Some ( new_nat_target) != group_entry. int_fwding . nat_target
1715+ || old_vlan_id != new_vlan_id
16951716 {
16961717 update_nat_tables (
16971718 s,
16981719 group_ip,
1699- Some ( new_group_info. internal_forwarding . nat_target . ok_or_else (
1700- || {
1701- DpdError :: Invalid (
1702- "external groups must have NAT target" . to_string ( ) ,
1703- )
1704- } ,
1705- ) ?) ,
1720+ Some ( new_nat_target) ,
17061721 group_entry. int_fwding . nat_target ,
1722+ old_vlan_id,
1723+ new_vlan_id,
17071724 ) ?;
17081725 }
17091726
1710- // Update VLAN if it changed
1711- if new_group_info. external_forwarding . vlan_id
1712- != group_entry. ext_fwding . vlan_id
1713- {
1727+ // Update route table VLAN if it changed
1728+ // Route tables use simple dst_addr matching but select forward vs forward_vlan action
1729+ if old_vlan_id != new_vlan_id {
17141730 match group_ip {
17151731 IpAddr :: V4 ( ipv4) => table:: mcast:: mcast_route:: update_ipv4_entry (
17161732 s,
17171733 ipv4,
1718- new_group_info. external_forwarding . vlan_id ,
1734+ old_vlan_id,
1735+ new_vlan_id,
17191736 ) ,
17201737 IpAddr :: V6 ( ipv6) => table:: mcast:: mcast_route:: update_ipv6_entry (
17211738 s,
17221739 ipv6,
1723- new_group_info. external_forwarding . vlan_id ,
1740+ old_vlan_id,
1741+ new_vlan_id,
17241742 ) ,
17251743 } ?;
17261744 }
@@ -1811,7 +1829,11 @@ fn delete_group_tables(
18111829 // (which have NAT targets).
18121830 if group. int_fwding . nat_target . is_some ( ) {
18131831 remove_ipv4_source_filters ( s, ipv4, group. sources . as_deref ( ) ) ?;
1814- table:: mcast:: mcast_nat:: del_ipv4_entry ( s, ipv4) ?;
1832+ table:: mcast:: mcast_nat:: del_ipv4_entry (
1833+ s,
1834+ ipv4,
1835+ group. ext_fwding . vlan_id ,
1836+ ) ?;
18151837 }
18161838
18171839 delete_group_bitmap_entries ( s, group) ?;
@@ -1825,7 +1847,11 @@ fn delete_group_tables(
18251847 // NAT targets). Internal groups don't have source filtering.
18261848 if group. int_fwding . nat_target . is_some ( ) {
18271849 remove_ipv6_source_filters ( s, ipv6, group. sources . as_deref ( ) ) ?;
1828- table:: mcast:: mcast_nat:: del_ipv6_entry ( s, ipv6) ?;
1850+ table:: mcast:: mcast_nat:: del_ipv6_entry (
1851+ s,
1852+ ipv6,
1853+ group. ext_fwding . vlan_id ,
1854+ ) ?;
18291855 }
18301856
18311857 delete_group_bitmap_entries ( s, group) ?;
@@ -1863,31 +1889,47 @@ fn update_nat_tables(
18631889 group_ip : IpAddr ,
18641890 new_nat_target : Option < NatTarget > ,
18651891 old_nat_target : Option < NatTarget > ,
1892+ old_vlan_id : Option < u16 > ,
1893+ new_vlan_id : Option < u16 > ,
18661894) -> DpdResult < ( ) > {
18671895 match ( group_ip, new_nat_target, old_nat_target) {
1868- ( IpAddr :: V4 ( ipv4) , Some ( nat) , Some ( _) ) => {
1869- // NAT to NAT - update existing entry
1870- table:: mcast:: mcast_nat:: update_ipv4_entry ( s, ipv4, nat)
1896+ ( IpAddr :: V4 ( ipv4) , Some ( new_nat) , Some ( old_nat) ) => {
1897+ // NAT to NAT - update existing entry (handles VLAN changes internally)
1898+ table:: mcast:: mcast_nat:: update_ipv4_entry (
1899+ s,
1900+ ipv4,
1901+ new_nat,
1902+ old_nat,
1903+ old_vlan_id,
1904+ new_vlan_id,
1905+ )
18711906 }
1872- ( IpAddr :: V6 ( ipv6) , Some ( nat) , Some ( _) ) => {
1873- // NAT to NAT - update existing entry
1874- table:: mcast:: mcast_nat:: update_ipv6_entry ( s, ipv6, nat)
1907+ ( IpAddr :: V6 ( ipv6) , Some ( new_nat) , Some ( old_nat) ) => {
1908+ // NAT to NAT - update existing entry (handles VLAN changes internally)
1909+ table:: mcast:: mcast_nat:: update_ipv6_entry (
1910+ s,
1911+ ipv6,
1912+ new_nat,
1913+ old_nat,
1914+ old_vlan_id,
1915+ new_vlan_id,
1916+ )
18751917 }
18761918 ( IpAddr :: V4 ( ipv4) , Some ( nat) , None ) => {
18771919 // No NAT to NAT - add new entry
1878- table:: mcast:: mcast_nat:: add_ipv4_entry ( s, ipv4, nat)
1920+ table:: mcast:: mcast_nat:: add_ipv4_entry ( s, ipv4, nat, new_vlan_id )
18791921 }
18801922 ( IpAddr :: V6 ( ipv6) , Some ( nat) , None ) => {
18811923 // No NAT to NAT - add new entry
1882- table:: mcast:: mcast_nat:: add_ipv6_entry ( s, ipv6, nat)
1924+ table:: mcast:: mcast_nat:: add_ipv6_entry ( s, ipv6, nat, new_vlan_id )
18831925 }
18841926 ( IpAddr :: V4 ( ipv4) , None , Some ( _) ) => {
18851927 // NAT to no NAT - delete entry
1886- table:: mcast:: mcast_nat:: del_ipv4_entry ( s, ipv4)
1928+ table:: mcast:: mcast_nat:: del_ipv4_entry ( s, ipv4, old_vlan_id )
18871929 }
18881930 ( IpAddr :: V6 ( ipv6) , None , Some ( _) ) => {
18891931 // NAT to no NAT - delete entry
1890- table:: mcast:: mcast_nat:: del_ipv6_entry ( s, ipv6)
1932+ table:: mcast:: mcast_nat:: del_ipv6_entry ( s, ipv6, old_vlan_id )
18911933 }
18921934 _ => Ok ( ( ) ) , // No change (None → None)
18931935 }
@@ -1934,33 +1976,49 @@ fn update_internal_group_bitmap_tables(
19341976/// Only updates the external bitmap entry since that's the only bitmap
19351977/// entry created during group configuration. The underlay replication
19361978/// is handled separately via the ASIC's multicast group primitives.
1979+ ///
1980+ /// # Arguments
1981+ ///
1982+ /// * `s` - Switch instance for table operations.
1983+ /// * `group_ip` - IP address of the multicast group.
1984+ /// * `external_group_id` - ID of the external multicast group for bitmap updates.
1985+ /// * `members` - Group members used to recreate port bitmap.
1986+ /// * `current_vlan_id` - VLAN currently in the table (may be the attempted new VLAN).
1987+ /// * `target_vlan_id` - VLAN to restore to (the original VLAN).
19371988fn update_fwding_tables (
19381989 s : & Switch ,
19391990 group_ip : IpAddr ,
19401991 external_group_id : MulticastGroupId ,
19411992 members : & [ MulticastGroupMember ] ,
1942- vlan_id : Option < u16 > ,
1993+ current_vlan_id : Option < u16 > ,
1994+ target_vlan_id : Option < u16 > ,
19431995) -> DpdResult < ( ) > {
19441996 match group_ip {
1945- IpAddr :: V4 ( ipv4) => {
1946- table:: mcast:: mcast_route:: update_ipv4_entry ( s, ipv4, vlan_id)
1947- }
1948- IpAddr :: V6 ( ipv6) => {
1949- table:: mcast:: mcast_route:: update_ipv6_entry ( s, ipv6, vlan_id)
1950- . and_then ( |_| {
1951- // Update external bitmap for external members
1952- // (only external bitmap entries exist; underlay replication
1953- // uses ASIC multicast groups directly)
1954- let external_port_bitmap =
1955- create_port_bitmap ( members, Direction :: External ) ;
1956- table:: mcast:: mcast_egress:: update_bitmap_entry (
1957- s,
1958- external_group_id,
1959- & external_port_bitmap,
1960- vlan_id,
1961- )
1962- } )
1963- }
1997+ IpAddr :: V4 ( ipv4) => table:: mcast:: mcast_route:: update_ipv4_entry (
1998+ s,
1999+ ipv4,
2000+ current_vlan_id,
2001+ target_vlan_id,
2002+ ) ,
2003+ IpAddr :: V6 ( ipv6) => table:: mcast:: mcast_route:: update_ipv6_entry (
2004+ s,
2005+ ipv6,
2006+ current_vlan_id,
2007+ target_vlan_id,
2008+ )
2009+ . and_then ( |_| {
2010+ // Update external bitmap for external members
2011+ // (only external bitmap entries exist, underlay replication
2012+ // uses ASIC multicast groups directly)
2013+ let external_port_bitmap =
2014+ create_port_bitmap ( members, Direction :: External ) ;
2015+ table:: mcast:: mcast_egress:: update_bitmap_entry (
2016+ s,
2017+ external_group_id,
2018+ & external_port_bitmap,
2019+ target_vlan_id,
2020+ )
2021+ } ) ,
19642022 }
19652023}
19662024
0 commit comments