Skip to content

Commit 9fe1457

Browse files
authored
Implement BGP configuration for VLAN interfaces with FHRP support (#1810)
Add comprehensive BGP neighbor configuration for VLAN-based Layer 3 switching scenarios with proper handling of untagged VLAN members and First Hop Redundancy Protocol (FHRP) virtual IP addresses. Changes: 1. Exclude untagged VLAN members from physical interface BGP configuration - Add _is_untagged_vlan_member() helper function to detect VLAN membership - Physical interfaces that are untagged VLAN members excluded from BGP_NEIGHBOR - Physical interfaces that are untagged VLAN members excluded from BGP_NEIGHBOR_AF - BGP peering happens over VLAN interface (SVI) instead of physical interface 2. Add BGP configuration for VLAN interfaces using peer IP addresses - VLAN interfaces with untagged members get BGP_NEIGHBOR entries - Use peer IP addresses from connected interfaces as neighbor identifiers - Support FHRP virtual IP addresses (VRRP/HSRP/GLBP) via existing detection logic - Fallback to direct IP addresses if no FHRP VIP configured - Reuse get_connected_interface_ipv4_address() for consistent FHRP detection - Support both IPv4 and IPv6 address families - Create appropriate BGP_NEIGHBOR_AF entries (ipv4_unicast/ipv6_unicast) 3. Add VIP address caching and management - Load VIP addresses at sync start to avoid repeated queries - Cache management functions for performance optimization The combination of these changes ensures BGP neighbors are correctly configured for VLAN-based Layer 3 switching scenarios where: - Physical interfaces are Layer 2 VLAN members (untagged) - IP addressing and routing happens on VLAN interfaces (SVIs) - Redundant gateway configurations use FHRP protocols This implementation is critical for high-availability network deployments using VLAN interfaces with FHRP redundancy. Signed-off-by: Christian Berendt <[email protected]>
1 parent 697f381 commit 9fe1457

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed

osism/tasks/conductor/sonic/config_generator.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ def generate_sonic_config(device, hwsku, device_as_mapping=None, config_version=
226226
netbox_interfaces,
227227
transfer_ips,
228228
utils.nb,
229+
vlan_info,
229230
)
230231

231232
# Add NTP server configuration (device-specific)
@@ -830,6 +831,42 @@ def _has_transfer_role_ipv4(port_name, transfer_ips, netbox_interfaces):
830831
return False
831832

832833

834+
def _is_untagged_vlan_member(port_name, vlan_info, netbox_interfaces):
835+
"""Check if an interface is an untagged member of any VLAN.
836+
837+
When an interface is an untagged VLAN member, BGP peering should happen
838+
over the VLAN interface (SVI) instead of the physical interface.
839+
840+
Args:
841+
port_name: SONiC interface name (e.g., "Ethernet0")
842+
vlan_info: VLAN information dict with vlan_members structure
843+
netbox_interfaces: Dict mapping SONiC names to NetBox interface info
844+
845+
Returns:
846+
bool: True if interface is an untagged VLAN member, False otherwise
847+
"""
848+
if not vlan_info or not netbox_interfaces:
849+
return False
850+
851+
# Get the NetBox interface name for this SONiC port
852+
if port_name not in netbox_interfaces:
853+
return False
854+
855+
netbox_interface_name = netbox_interfaces[port_name]["netbox_name"]
856+
857+
# Check all VLANs to see if this interface is an untagged member
858+
for vid, members in vlan_info.get("vlan_members", {}).items():
859+
if netbox_interface_name in members:
860+
tagging_mode = members[netbox_interface_name]
861+
if tagging_mode == "untagged":
862+
logger.debug(
863+
f"Interface {port_name} ({netbox_interface_name}) is untagged member of VLAN {vid}"
864+
)
865+
return True
866+
867+
return False
868+
869+
833870
def _add_bgp_configurations(
834871
config,
835872
connected_interfaces,
@@ -841,6 +878,7 @@ def _add_bgp_configurations(
841878
netbox_interfaces=None,
842879
transfer_ips=None,
843880
netbox=None,
881+
vlan_info=None,
844882
):
845883
"""Add BGP configurations.
846884
@@ -855,6 +893,7 @@ def _add_bgp_configurations(
855893
netbox_interfaces: Dict mapping SONiC names to NetBox interface info
856894
transfer_ips: Dict of IPv4 addresses from transfer role prefixes
857895
netbox: NetBox API client for querying connected interface IPs
896+
vlan_info: VLAN information dict for checking untagged VLAN membership
858897
"""
859898
# Add BGP_NEIGHBOR_AF configuration for connected interfaces
860899
for port_name in config["PORT"]:
@@ -864,11 +903,21 @@ def _add_bgp_configurations(
864903
has_transfer_ipv4 = _has_transfer_role_ipv4(
865904
port_name, transfer_ips, netbox_interfaces
866905
)
906+
is_untagged_vlan_member = _is_untagged_vlan_member(
907+
port_name, vlan_info, netbox_interfaces
908+
)
867909

868910
if (
869911
port_name in connected_interfaces
870912
and port_name not in portchannel_info["member_mapping"]
871913
):
914+
# Skip interfaces that are untagged VLAN members - BGP peering happens over VLAN interface
915+
if is_untagged_vlan_member:
916+
logger.info(
917+
f"Excluding interface {port_name} from BGP configuration (untagged VLAN member)"
918+
)
919+
continue
920+
872921
# Include interfaces with transfer role IPv4 or no direct IPv4
873922
if has_transfer_ipv4 or not has_direct_ipv4:
874923
# Try to get the IPv4 address of the connected endpoint interface
@@ -925,11 +974,21 @@ def _add_bgp_configurations(
925974
has_transfer_ipv4 = _has_transfer_role_ipv4(
926975
port_name, transfer_ips, netbox_interfaces
927976
)
977+
is_untagged_vlan_member = _is_untagged_vlan_member(
978+
port_name, vlan_info, netbox_interfaces
979+
)
928980

929981
if (
930982
port_name in connected_interfaces
931983
and port_name not in portchannel_info["member_mapping"]
932984
):
985+
# Skip interfaces that are untagged VLAN members - BGP peering happens over VLAN interface
986+
if is_untagged_vlan_member:
987+
logger.info(
988+
f"Excluding interface {port_name} from BGP_NEIGHBOR configuration (untagged VLAN member)"
989+
)
990+
continue
991+
933992
# Include interfaces with transfer role IPv4 or no direct IPv4
934993
if has_transfer_ipv4 or not has_direct_ipv4:
935994
# Try to get the IPv4 address of the connected endpoint interface
@@ -1046,6 +1105,111 @@ def _add_bgp_configurations(
10461105

10471106
config["BGP_NEIGHBOR"][neighbor_key] = bgp_neighbor_config
10481107

1108+
# Add BGP configuration for VLAN interfaces (SVIs) based on peer IP addresses
1109+
# For each VLAN interface with IP addresses, find the connected peer interfaces
1110+
# and use their IP addresses (direct IP or FHRP VIP) as BGP neighbors
1111+
if vlan_info and "vlan_interfaces" in vlan_info and "vlan_members" in vlan_info:
1112+
for vid, vlan_interface_data in vlan_info["vlan_interfaces"].items():
1113+
if "addresses" not in vlan_interface_data:
1114+
continue
1115+
1116+
addresses = vlan_interface_data["addresses"]
1117+
if not addresses:
1118+
continue
1119+
1120+
# Find untagged member interfaces for this VLAN
1121+
# Only untagged members are relevant for VLAN BGP neighbors
1122+
if vid not in vlan_info["vlan_members"]:
1123+
logger.debug(
1124+
f"No VLAN members found for VLAN {vid}, skipping BGP configuration"
1125+
)
1126+
continue
1127+
1128+
vlan_members = vlan_info["vlan_members"][vid]
1129+
untagged_members = [
1130+
iface_name
1131+
for iface_name, tagging_mode in vlan_members.items()
1132+
if tagging_mode == "untagged"
1133+
]
1134+
1135+
if not untagged_members:
1136+
logger.debug(
1137+
f"No untagged members found for VLAN {vid}, skipping BGP configuration"
1138+
)
1139+
continue
1140+
1141+
# For each untagged member interface, get the peer IP address
1142+
# and create BGP neighbor with the peer IP (not local VLAN IP!)
1143+
peer_ips_found = set() # Track unique peer IPs to avoid duplicates
1144+
1145+
for netbox_iface_name in untagged_members:
1146+
# Convert NetBox interface name to SONiC name
1147+
sonic_iface_name = None
1148+
if netbox_interfaces:
1149+
for sonic_name, iface_info in netbox_interfaces.items():
1150+
if iface_info.get("netbox_name") == netbox_iface_name:
1151+
sonic_iface_name = sonic_name
1152+
break
1153+
1154+
if not sonic_iface_name:
1155+
logger.debug(
1156+
f"Could not find SONiC name for NetBox interface {netbox_iface_name} "
1157+
f"in VLAN {vid}, skipping"
1158+
)
1159+
continue
1160+
1161+
# Get peer IP address using the existing FHRP VIP detection logic
1162+
peer_ipv4 = None
1163+
if netbox:
1164+
peer_ipv4 = get_connected_interface_ipv4_address(
1165+
device, sonic_iface_name, netbox
1166+
)
1167+
1168+
if peer_ipv4:
1169+
# Avoid duplicate peer IPs across multiple untagged members
1170+
if peer_ipv4 in peer_ips_found:
1171+
logger.debug(
1172+
f"Peer IP {peer_ipv4} already configured for VLAN {vid}, "
1173+
f"skipping duplicate from interface {sonic_iface_name}"
1174+
)
1175+
continue
1176+
1177+
peer_ips_found.add(peer_ipv4)
1178+
1179+
# Create BGP neighbor with peer IP address (FHRP VIP or direct IP)
1180+
neighbor_key = f"default|{peer_ipv4}"
1181+
1182+
# Determine peer_type - for VLAN interfaces, default to external
1183+
peer_type = "external"
1184+
1185+
# Set v6only=false for IPv4 BGP neighbor
1186+
bgp_neighbor_config = {
1187+
"peer_type": peer_type,
1188+
"v6only": "false",
1189+
}
1190+
1191+
config["BGP_NEIGHBOR"][neighbor_key] = bgp_neighbor_config
1192+
1193+
# Add BGP_NEIGHBOR_AF for IPv4 unicast
1194+
ipv4_af_key = f"default|{peer_ipv4}|ipv4_unicast"
1195+
config["BGP_NEIGHBOR_AF"][ipv4_af_key] = {"admin_status": "true"}
1196+
1197+
logger.info(
1198+
f"Added BGP neighbor configuration for VLAN {vid} using peer IP {peer_ipv4} "
1199+
f"from connected interface {sonic_iface_name} (NetBox: {netbox_iface_name})"
1200+
)
1201+
else:
1202+
logger.debug(
1203+
f"No peer IPv4 address found for interface {sonic_iface_name} "
1204+
f"(NetBox: {netbox_iface_name}) in VLAN {vid}"
1205+
)
1206+
1207+
if not peer_ips_found:
1208+
logger.warning(
1209+
f"No peer IP addresses found for any untagged member of VLAN {vid}, "
1210+
f"no BGP neighbors configured"
1211+
)
1212+
10491213

10501214
def _get_connected_device_for_interface(device, interface_name):
10511215
"""Get the connected device for a given interface name.

0 commit comments

Comments
 (0)