From f484470ee33881057f9da2a9ad5bf85f5684367a Mon Sep 17 00:00:00 2001 From: brian Date: Tue, 19 May 2026 17:50:37 -0400 Subject: [PATCH] fix(api,agent): Add anycast prefixes filter to routing-profiles --- crates/admin-cli/src/tenant/update/args.rs | 3 +- crates/agent/src/ethernet_virtualization.rs | 12 ++++ crates/agent/src/nvue.rs | 26 ++++++++- crates/agent/src/tests/full.rs | 1 + crates/agent/templates/nvue_startup_fnn.conf | 50 ++++++++++------- .../full_nvue_startup_fnn_l3.yaml.expected | 22 ++++---- .../nvue_build_fnn_dual_stack.yaml.expected | 12 ++-- .../nvue_build_fnn_ipv6_acls.yaml.expected | 12 ++-- ...nvue_build_fnn_ipv6_only_vpc.yaml.expected | 12 ++-- ...ue_build_fnn_multi_port_ipv6.yaml.expected | 12 ++-- .../nvue_startup_fnn_classic.yaml.expected | 40 +++++++++---- ..._with_empty_nsg_default_deny.yaml.expected | 40 +++++++++---- .../nvue_startup_fnn_with_leaks.yaml.expected | 56 ++++++++++++------- ...nvue_startup_quarantined_fnn.yaml.expected | 40 +++++++++---- crates/api/src/cfg/README.md | 3 +- crates/api/src/cfg/file.rs | 5 ++ crates/api/src/handlers/dpu.rs | 7 +++ .../api/src/tests/common/api_fixtures/mod.rs | 2 + crates/api/src/tests/machine_network.rs | 21 ++++++- crates/api/src/tests/vpc.rs | 2 + crates/rpc/proto/forge.proto | 4 ++ 21 files changed, 271 insertions(+), 111 deletions(-) diff --git a/crates/admin-cli/src/tenant/update/args.rs b/crates/admin-cli/src/tenant/update/args.rs index b5df63cfb2..3c96ecb1a3 100644 --- a/crates/admin-cli/src/tenant/update/args.rs +++ b/crates/admin-cli/src/tenant/update/args.rs @@ -25,8 +25,7 @@ pub struct Args { #[clap( short = 'p', long, - help = "Optional, routing profile name to apply to the tenant", - default_value(None) + help = "Optional, routing profile name to apply to the tenant" )] pub routing_profile_type: Option, diff --git a/crates/agent/src/ethernet_virtualization.rs b/crates/agent/src/ethernet_virtualization.rs index ff23c73513..eaa94eb5b3 100644 --- a/crates/agent/src/ethernet_virtualization.rs +++ b/crates/agent/src/ethernet_virtualization.rs @@ -470,6 +470,11 @@ pub async fn update_nvue( .iter() .map(|l| l.prefix.to_owned()) .collect(), + allowed_anycast_prefixes: rp + .allowed_anycast_prefixes + .iter() + .map(|p| p.prefix.to_owned()) + .collect(), }) }, bgp_leaf_session_password: nc.bgp_leaf_session_password.clone(), @@ -2590,6 +2595,9 @@ mod tests { } else { vec![] }, + allowed_anycast_prefixes: vec![rpc::PrefixFilterPolicyEntry { + prefix: "5.255.254.0/24".to_string(), + }], route_target_imports: vec![rpc_common::RouteTarget { asn: 44444, vni: 55555, @@ -2844,6 +2852,7 @@ mod tests { leak_default_route_from_underlay: false, leak_tenant_host_routes_to_underlay: false, accepted_leaks_from_underlay: vec![], + allowed_anycast_prefixes: vec!["5.255.254.0/24".to_string()], route_target_imports: vec![nvue::RouteTargetConfig { asn: 44444, vni: 55555, @@ -3079,6 +3088,9 @@ mod tests { leak_default_route_from_underlay: false, leak_tenant_host_routes_to_underlay: false, accepted_leaks_from_underlay: vec![], + allowed_anycast_prefixes: vec![rpc::PrefixFilterPolicyEntry { + prefix: "5.255.254.0/24".to_string(), + }], route_target_imports: vec![rpc_common::RouteTarget { asn: 44444, vni: 55555, diff --git a/crates/agent/src/nvue.rs b/crates/agent/src/nvue.rs index cca4ca4143..2ce4342dff 100644 --- a/crates/agent/src/nvue.rs +++ b/crates/agent/src/nvue.rs @@ -127,6 +127,8 @@ pub fn build(conf: NvueConfig) -> eyre::Result { // and make a later transition easier. let routing_profile = conf.ct_routing_profile.as_ref().map(|rt| { let (v4leaks, v6leaks) = split_prefixes_by_family(&rt.accepted_leaks_from_underlay, 1); + let (v4allowed_anycast, v6allowed_anycast) = + split_prefixes_by_family(&rt.allowed_anycast_prefixes, 1); TmplRoutingProfile { TenantLeakCommunitiesAccepted: rt.tenant_leak_communities_accepted, @@ -134,6 +136,8 @@ pub fn build(conf: NvueConfig) -> eyre::Result { LeakTenantHostRoutesToUnderlay: rt.leak_tenant_host_routes_to_underlay, AcceptedLeaksFromUnderlayIpv4: v4leaks, AcceptedLeaksFromUnderlayIpv6: v6leaks, + AllowedAnycastPrefixesIpv4: v4allowed_anycast, + AllowedAnycastPrefixesIpv6: v6allowed_anycast, RouteTargetImports: rt .route_target_imports .iter() @@ -346,6 +350,14 @@ pub fn build(conf: NvueConfig) -> eyre::Result { .iter() .map(|vni| TmplVni { Vni: *vni }) .collect(), + HasAllowedAnycastPrefixesIpv4: routing_profile + .as_ref() + .map(|p| !p.AllowedAnycastPrefixesIpv4.is_empty()) + .unwrap_or_default(), + HasAllowedAnycastPrefixesIpv6: routing_profile + .as_ref() + .map(|p| !p.AllowedAnycastPrefixesIpv6.is_empty()) + .unwrap_or_default(), RoutingProfile: routing_profile.clone(), PortPrefixes: port.VpcPrefixes.clone(), PortPrefixesIpv6: port.VpcPrefixesIpv6.clone(), @@ -1008,6 +1020,8 @@ pub struct RoutingProfile { pub route_targets_on_exports: Vec, pub tenant_leak_communities_accepted: bool, pub accepted_leaks_from_underlay: Vec, + #[serde(default)] + pub allowed_anycast_prefixes: Vec, } #[derive(Clone, Deserialize, Debug)] @@ -1297,6 +1311,8 @@ struct TmplRoutingProfile { TenantLeakCommunitiesAccepted: bool, AcceptedLeaksFromUnderlayIpv4: Vec, AcceptedLeaksFromUnderlayIpv6: Vec, + AllowedAnycastPrefixesIpv4: Vec, + AllowedAnycastPrefixesIpv6: Vec, } #[allow(non_snake_case)] @@ -1407,6 +1423,9 @@ struct TmplVpc { HasVpcPeerVnis: bool, VpcPeerVnis: Vec, + HasAllowedAnycastPrefixesIpv4: bool, + HasAllowedAnycastPrefixesIpv6: bool, + RoutingProfile: Option, } @@ -1418,7 +1437,7 @@ struct TmplHostInterfaces { /// IPv6 host address (if dual-stack). HostIPv6: Option, - // HostRoute in the context of FNN-L3 is the /30 prefix allocation. + // HostRoute in the context of FNN-L3 is the /31 prefix allocation. // This used to be populated as the HostIP + "/32", but then with // the advent of interface prefix allocations (where ETV is just a /32, // and FNN-L3 is a /31), HostRoute became the allocation (which was @@ -1717,6 +1736,7 @@ mod tests { route_target_imports: vec![], route_targets_on_exports: vec![], accepted_leaks_from_underlay: vec![], + allowed_anycast_prefixes: vec![], }); conf.ct_port_configs = vec![PortConfig { interface_name: "pf0vf0_if".into(), @@ -1798,6 +1818,7 @@ mod tests { route_target_imports: vec![], route_targets_on_exports: vec![], accepted_leaks_from_underlay: vec![], + allowed_anycast_prefixes: vec![], }); conf.ct_port_configs = vec![PortConfig { interface_name: "pf0vf0_if".into(), @@ -1882,6 +1903,7 @@ mod tests { route_target_imports: vec![], route_targets_on_exports: vec![], accepted_leaks_from_underlay: vec![], + allowed_anycast_prefixes: vec![], }); conf.ct_port_configs = vec![ PortConfig { @@ -1953,6 +1975,7 @@ mod tests { route_target_imports: vec![], route_targets_on_exports: vec![], accepted_leaks_from_underlay: vec![], + allowed_anycast_prefixes: vec![], }); conf.ct_port_configs = vec![PortConfig { interface_name: "pf0vf0_if".into(), @@ -2115,6 +2138,7 @@ mod tests { route_target_imports: vec![], route_targets_on_exports: vec![], accepted_leaks_from_underlay: vec![], + allowed_anycast_prefixes: vec![], } } diff --git a/crates/agent/src/tests/full.rs b/crates/agent/src/tests/full.rs index 965fc53386..ee129ecb94 100644 --- a/crates/agent/src/tests/full.rs +++ b/crates/agent/src/tests/full.rs @@ -842,6 +842,7 @@ async fn handle_netconf(AxumState(state): AxumState>>) -> impl leak_default_route_from_underlay: false, leak_tenant_host_routes_to_underlay: false, accepted_leaks_from_underlay: vec![], + allowed_anycast_prefixes: vec![], route_target_imports: vec![rpc_common::RouteTarget { asn: 44444, vni: 55555, diff --git a/crates/agent/templates/nvue_startup_fnn.conf b/crates/agent/templates/nvue_startup_fnn.conf index 3899164727..788eab65a1 100644 --- a/crates/agent/templates/nvue_startup_fnn.conf +++ b/crates/agent/templates/nvue_startup_fnn.conf @@ -229,41 +229,53 @@ action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST: + {{- range $vpc := $tenant.Vpcs }} + DPU_FROM_INSTANCE_PREFIX_LIST_{{ $vpc.VrfName }}: rule: - {{- range $nvueConfig.AnycastSitePrefixes }} + {{- if eq (len $vpc.RoutingProfile.AllowedAnycastPrefixesIpv4) 0 }}{{/* This can be removed after a version or two to allow a grace/deprecation period. */}} + {{- range $nvueConfig.AnycastSitePrefixes }} + '{{ .Index }}': + action: permit + match: + {{ .Prefix }}: + max-prefix-len: 32 + {{- end }} + {{- else }} + {{- range $vpc.RoutingProfile.AllowedAnycastPrefixesIpv4 }} '{{ .Index }}': action: permit match: {{ .Prefix }}: - min-prefix-len: 32 + max-prefix-len: 32 + {{- end }} {{- end }} '65535': action: deny match: any: {} - DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_{{ $vpc.VrfName }}: + type: ipv6 rule: - {{- range $nvueConfig.TrafficInterceptPublicPrefixes }} + {{- range $vpc.RoutingProfile.AllowedAnycastPrefixesIpv6 }} '{{ .Index }}': action: permit match: - {{ .Prefix }}: {} - {{- end }} + {{ .Prefix }}: + max-prefix-len: 128 + {{- end }} '65535': action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST_IPV6: - type: ipv6 + {{- end }} + DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: rule: - {{- range $nvueConfig.AnycastSitePrefixesIpv6 }} + {{- range $nvueConfig.TrafficInterceptPublicPrefixes }} '{{ .Index }}': action: permit match: - {{ .Prefix }}: - min-prefix-len: 128 - {{- end }} + {{ .Prefix }}: {} + {{- end }} '65535': action: deny match: @@ -472,7 +484,7 @@ permit: {}{{/* We allow it in because we might need to leak to some targets and drop to others, so we tag it for later. */}} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_{{ $vpc.VrfName }} community-list: BYOIP_LEAK_AND_DROP_EVPN_COMMUNITY_LIST set: tag: 65102{{/* tag it so we can match on the tag and use it for leaking and dropping later */}} @@ -485,7 +497,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_{{ $vpc.VrfName }} community-list: BYOIP_LEAK_COMMUNITY_LIST set: tag: 65100{{/* tag it so we can match on the tag and use it for leaking later */}} @@ -499,7 +511,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_{{ $vpc.VrfName }} set: tag: 65101{{/* tag it so we can match on the tag and use it for leaking later */}} community: @@ -517,7 +529,7 @@ permit: {}{{/* We allow it in because we might need to leak to some targets and drop to others, so we tag it for later. */}} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_{{ $vpc.VrfName }} community-list: BYOIP_LEAK_AND_DROP_EVPN_COMMUNITY_LIST set: tag: 65102{{/* tag it so we can match on the tag and use it for leaking and dropping later */}} @@ -530,7 +542,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_{{ $vpc.VrfName }} community-list: BYOIP_LEAK_COMMUNITY_LIST set: tag: 65100 @@ -544,7 +556,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_{{ $vpc.VrfName }} set: tag: 65101 community: diff --git a/crates/agent/templates/tests/full_nvue_startup_fnn_l3.yaml.expected b/crates/agent/templates/tests/full_nvue_startup_fnn_l3.yaml.expected index 8982561836..f541b565b9 100644 --- a/crates/agent/templates/tests/full_nvue_startup_fnn_l3.yaml.expected +++ b/crates/agent/templates/tests/full_nvue_startup_fnn_l3.yaml.expected @@ -96,30 +96,30 @@ action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_10101: rule: '1000': action: permit match: 5.255.255.0/24: - min-prefix-len: 32 + max-prefix-len: 32 '65535': action: deny match: any: {} - DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_10101: + type: ipv6 rule: - '1': - action: permit - match: - 7.8.0.0/16: {} '65535': action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST_IPV6: - type: ipv6 + DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: rule: + '1': + action: permit + match: + 7.8.0.0/16: {} '65535': action: deny match: @@ -273,7 +273,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_10101 set: tag: 65101 community: @@ -290,7 +290,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_10101 set: tag: 65101 community: diff --git a/crates/agent/templates/tests/nvue_build_fnn_dual_stack.yaml.expected b/crates/agent/templates/tests/nvue_build_fnn_dual_stack.yaml.expected index 87ebd6a90e..9fbf26ba88 100644 --- a/crates/agent/templates/tests/nvue_build_fnn_dual_stack.yaml.expected +++ b/crates/agent/templates/tests/nvue_build_fnn_dual_stack.yaml.expected @@ -74,20 +74,20 @@ action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_100: rule: '65535': action: deny match: any: {} - DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_100: + type: ipv6 rule: '65535': action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST_IPV6: - type: ipv6 + DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: rule: '65535': action: deny @@ -242,7 +242,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_100 set: tag: 65101 community: @@ -259,7 +259,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_100 set: tag: 65101 community: diff --git a/crates/agent/templates/tests/nvue_build_fnn_ipv6_acls.yaml.expected b/crates/agent/templates/tests/nvue_build_fnn_ipv6_acls.yaml.expected index f01524e108..c392a4762c 100644 --- a/crates/agent/templates/tests/nvue_build_fnn_ipv6_acls.yaml.expected +++ b/crates/agent/templates/tests/nvue_build_fnn_ipv6_acls.yaml.expected @@ -85,20 +85,20 @@ action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_100: rule: '65535': action: deny match: any: {} - DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_100: + type: ipv6 rule: '65535': action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST_IPV6: - type: ipv6 + DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: rule: '65535': action: deny @@ -253,7 +253,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_100 set: tag: 65101 community: @@ -270,7 +270,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_100 set: tag: 65101 community: diff --git a/crates/agent/templates/tests/nvue_build_fnn_ipv6_only_vpc.yaml.expected b/crates/agent/templates/tests/nvue_build_fnn_ipv6_only_vpc.yaml.expected index ecddaf5e21..20524561fa 100644 --- a/crates/agent/templates/tests/nvue_build_fnn_ipv6_only_vpc.yaml.expected +++ b/crates/agent/templates/tests/nvue_build_fnn_ipv6_only_vpc.yaml.expected @@ -75,20 +75,20 @@ action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_100: rule: '65535': action: deny match: any: {} - DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_100: + type: ipv6 rule: '65535': action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST_IPV6: - type: ipv6 + DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: rule: '65535': action: deny @@ -243,7 +243,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_100 set: tag: 65101 community: @@ -260,7 +260,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_100 set: tag: 65101 community: diff --git a/crates/agent/templates/tests/nvue_build_fnn_multi_port_ipv6.yaml.expected b/crates/agent/templates/tests/nvue_build_fnn_multi_port_ipv6.yaml.expected index 7936d55f3c..fdb04077d9 100644 --- a/crates/agent/templates/tests/nvue_build_fnn_multi_port_ipv6.yaml.expected +++ b/crates/agent/templates/tests/nvue_build_fnn_multi_port_ipv6.yaml.expected @@ -84,20 +84,20 @@ action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_200: rule: '65535': action: deny match: any: {} - DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_200: + type: ipv6 rule: '65535': action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST_IPV6: - type: ipv6 + DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: rule: '65535': action: deny @@ -252,7 +252,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_200 set: tag: 65101 community: @@ -269,7 +269,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_200 set: tag: 65101 community: diff --git a/crates/agent/templates/tests/nvue_startup_fnn_classic.yaml.expected b/crates/agent/templates/tests/nvue_startup_fnn_classic.yaml.expected index 33275d670b..b4a2f20acd 100644 --- a/crates/agent/templates/tests/nvue_startup_fnn_classic.yaml.expected +++ b/crates/agent/templates/tests/nvue_startup_fnn_classic.yaml.expected @@ -127,34 +127,52 @@ action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025186: rule: - '1000': + '1': action: permit match: - 5.255.255.0/24: - min-prefix-len: 32 + 5.255.254.0/24: + max-prefix-len: 32 '65535': action: deny match: any: {} - DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025186: + type: ipv6 + rule: + '65535': + action: deny + match: + any: {} + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025197: rule: '1': action: permit match: - 7.6.5.0/24: {} + 5.255.254.0/24: + max-prefix-len: 32 '65535': action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST_IPV6: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025197: type: ipv6 rule: '65535': action: deny match: any: {} + DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + rule: + '1': + action: permit + match: + 7.6.5.0/24: {} + '65535': + action: deny + match: + any: {} DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST_IPV6: type: ipv6 rule: @@ -300,7 +318,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025186 set: tag: 65101 community: @@ -317,7 +335,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025186 set: tag: 65101 community: @@ -334,7 +352,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025197 set: tag: 65101 community: @@ -351,7 +369,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025197 set: tag: 65101 community: diff --git a/crates/agent/templates/tests/nvue_startup_fnn_classic_with_empty_nsg_default_deny.yaml.expected b/crates/agent/templates/tests/nvue_startup_fnn_classic_with_empty_nsg_default_deny.yaml.expected index 60c45bb5b9..f23f68e2f0 100644 --- a/crates/agent/templates/tests/nvue_startup_fnn_classic_with_empty_nsg_default_deny.yaml.expected +++ b/crates/agent/templates/tests/nvue_startup_fnn_classic_with_empty_nsg_default_deny.yaml.expected @@ -135,34 +135,52 @@ action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025186: rule: - '1000': + '1': action: permit match: - 5.255.255.0/24: - min-prefix-len: 32 + 5.255.254.0/24: + max-prefix-len: 32 '65535': action: deny match: any: {} - DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025186: + type: ipv6 + rule: + '65535': + action: deny + match: + any: {} + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025197: rule: '1': action: permit match: - 7.6.5.0/24: {} + 5.255.254.0/24: + max-prefix-len: 32 '65535': action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST_IPV6: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025197: type: ipv6 rule: '65535': action: deny match: any: {} + DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + rule: + '1': + action: permit + match: + 7.6.5.0/24: {} + '65535': + action: deny + match: + any: {} DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST_IPV6: type: ipv6 rule: @@ -308,7 +326,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025186 set: tag: 65101 community: @@ -325,7 +343,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025186 set: tag: 65101 community: @@ -342,7 +360,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025197 set: tag: 65101 community: @@ -359,7 +377,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025197 set: tag: 65101 community: diff --git a/crates/agent/templates/tests/nvue_startup_fnn_with_leaks.yaml.expected b/crates/agent/templates/tests/nvue_startup_fnn_with_leaks.yaml.expected index cbeadbc021..83d71b222b 100644 --- a/crates/agent/templates/tests/nvue_startup_fnn_with_leaks.yaml.expected +++ b/crates/agent/templates/tests/nvue_startup_fnn_with_leaks.yaml.expected @@ -132,34 +132,52 @@ action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025186: rule: - '1000': + '1': action: permit match: - 5.255.255.0/24: - min-prefix-len: 32 + 5.255.254.0/24: + max-prefix-len: 32 '65535': action: deny match: any: {} - DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025186: + type: ipv6 + rule: + '65535': + action: deny + match: + any: {} + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025197: rule: '1': action: permit match: - 7.6.5.0/24: {} + 5.255.254.0/24: + max-prefix-len: 32 '65535': action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST_IPV6: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025197: type: ipv6 rule: '65535': action: deny match: any: {} + DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + rule: + '1': + action: permit + match: + 7.6.5.0/24: {} + '65535': + action: deny + match: + any: {} DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST_IPV6: type: ipv6 rule: @@ -341,7 +359,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025186 community-list: BYOIP_LEAK_AND_DROP_EVPN_COMMUNITY_LIST set: tag: 65102 @@ -354,7 +372,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025186 community-list: BYOIP_LEAK_COMMUNITY_LIST set: tag: 65100 @@ -367,7 +385,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025186 set: tag: 65101 community: @@ -384,7 +402,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025186 community-list: BYOIP_LEAK_AND_DROP_EVPN_COMMUNITY_LIST set: tag: 65102 @@ -397,7 +415,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025186 community-list: BYOIP_LEAK_COMMUNITY_LIST set: tag: 65100 @@ -410,7 +428,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025186 set: tag: 65101 community: @@ -427,7 +445,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025197 community-list: BYOIP_LEAK_AND_DROP_EVPN_COMMUNITY_LIST set: tag: 65102 @@ -440,7 +458,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025197 community-list: BYOIP_LEAK_COMMUNITY_LIST set: tag: 65100 @@ -453,7 +471,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025197 set: tag: 65101 community: @@ -470,7 +488,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025197 community-list: BYOIP_LEAK_AND_DROP_EVPN_COMMUNITY_LIST set: tag: 65102 @@ -483,7 +501,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025197 community-list: BYOIP_LEAK_COMMUNITY_LIST set: tag: 65100 @@ -496,7 +514,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025197 set: tag: 65101 community: diff --git a/crates/agent/templates/tests/nvue_startup_quarantined_fnn.yaml.expected b/crates/agent/templates/tests/nvue_startup_quarantined_fnn.yaml.expected index 3e7c378393..e797cd5826 100644 --- a/crates/agent/templates/tests/nvue_startup_quarantined_fnn.yaml.expected +++ b/crates/agent/templates/tests/nvue_startup_quarantined_fnn.yaml.expected @@ -147,34 +147,52 @@ action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025186: rule: - '1000': + '1': action: permit match: - 5.255.255.0/24: - min-prefix-len: 32 + 5.255.254.0/24: + max-prefix-len: 32 '65535': action: deny match: any: {} - DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025186: + type: ipv6 + rule: + '65535': + action: deny + match: + any: {} + DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025197: rule: '1': action: permit match: - 7.6.5.0/24: {} + 5.255.254.0/24: + max-prefix-len: 32 '65535': action: deny match: any: {} - DPU_FROM_INSTANCE_PREFIX_LIST_IPV6: + DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025197: type: ipv6 rule: '65535': action: deny match: any: {} + DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST: + rule: + '1': + action: permit + match: + 7.6.5.0/24: {} + '65535': + action: deny + match: + any: {} DPU_FROM_TRAFFIC_INTERCEPT_PEER_PREFIX_LIST_IPV6: type: ipv6 rule: @@ -320,7 +338,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025186 set: tag: 65101 community: @@ -337,7 +355,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025186 set: tag: 65101 community: @@ -354,7 +372,7 @@ permit: {} match: type: ipv4 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_vpc_1025197 set: tag: 65101 community: @@ -371,7 +389,7 @@ permit: {} match: type: ipv6 - ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6 + ip-prefix-list: DPU_FROM_INSTANCE_PREFIX_LIST_IPV6_vpc_1025197 set: tag: 65101 community: diff --git a/crates/api/src/cfg/README.md b/crates/api/src/cfg/README.md index 3dc1cd5a18..0f07ef2b5d 100644 --- a/crates/api/src/cfg/README.md +++ b/crates/api/src/cfg/README.md @@ -24,7 +24,7 @@ applicable. | `enable_route_servers` | `bool` | `false` | Enables route server injection into DPU FRR configs for L2VPN. | | `deny_prefixes` | `Vec` | `[]` | IPv4 CIDR prefixes that tenant instances are blocked from reaching. Generates iptables DROP rules and nvue ACL policies. | | `site_fabric_prefixes` | `Vec` | `[]` | IP prefixes (v4/v6) assigned for tenant use within this site. | -| `anycast_site_prefixes` | `Vec` | `[]` | Aggregate IPv4 prefixes containing tenant-announced prefixes (e.g., BYOIP). | +| `anycast_site_prefixes` | `Vec` | `[]` | Aggregate IPv4 prefixes containing tenant-announced prefixes (e.g., BYOIP). **Deprecated.** Use [`routing_profiles.allowed_anycast_prefixes`](#fnnroutingprofileconfig) instead. | | `common_tenant_host_asn` | `Option` | — | ASN that tenants use to peer with the DPU. If unset, any ASN is accepted. | | `vpc_isolation_behavior` | `VpcIsolationBehaviorType` | `MutualIsolation` | VPC isolation policy: `mutual_isolation` or `open`. | | `dpu_network_monitor_pinger_type` | `Option` | — | Pinger implementation type (e.g., `"OobNetBind"`) for DPU link health checks. | @@ -274,6 +274,7 @@ Extends `StateControllerConfig` with: | `leak_tenant_host_routes_to_underlay` | `bool` | `false` | Leak tenant host routes into the underlay/default VRF. | | `tenant_leak_communities_accepted` | `bool` | `false` | Honor route-leak communities sent by the tenant host OS. | | `accepted_leaks_from_underlay` | `Vec` | `[]` | Specific underlay/default VRF prefixes allowed to leak into tenant VRFs. Routing only; does not affect ACLs. | +| `allowed_anycast_prefixes` | `Vec` | `[]` | IPv4 or IPv6 prefixes that tenant hosts are allowed to announce to the DPU as anycast routes. | | `access_tier` | `u32` | `0` | Routing profile access tier. Lower values grant broader access. | ### `PrefixFilterPolicyEntry` diff --git a/crates/api/src/cfg/file.rs b/crates/api/src/cfg/file.rs index e2551a05a6..be6575c788 100644 --- a/crates/api/src/cfg/file.rs +++ b/crates/api/src/cfg/file.rs @@ -1017,6 +1017,11 @@ pub struct FnnRoutingProfileConfig { #[serde(default)] pub accepted_leaks_from_underlay: Vec, + /// Prefixes that tenant hosts are allowed to announce + /// to the DPU as anycast routes. + #[serde(default)] + pub allowed_anycast_prefixes: Vec, + /// Currently controls which profiles a tenant can use /// when creating VPCs. Lower value means broader access. /// A tenant can create a VPC with a routing profile of the same or broader access. diff --git a/crates/api/src/handlers/dpu.rs b/crates/api/src/handlers/dpu.rs index 7b680e1b99..b6f6131ba4 100644 --- a/crates/api/src/handlers/dpu.rs +++ b/crates/api/src/handlers/dpu.rs @@ -682,6 +682,13 @@ pub(crate) async fn get_managed_host_network_config_inner( prefix: l.prefix.to_string(), }) .collect(), + allowed_anycast_prefixes: p + .allowed_anycast_prefixes + .iter() + .map(|l| rpc::PrefixFilterPolicyEntry { + prefix: l.prefix.to_string(), + }) + .collect(), route_target_imports: p .route_target_imports .iter() diff --git a/crates/api/src/tests/common/api_fixtures/mod.rs b/crates/api/src/tests/common/api_fixtures/mod.rs index 1cf1c2e9cc..73c205ecc8 100644 --- a/crates/api/src/tests/common/api_fixtures/mod.rs +++ b/crates/api/src/tests/common/api_fixtures/mod.rs @@ -301,6 +301,7 @@ impl TestEnvOverrides { leak_tenant_host_routes_to_underlay: false, tenant_leak_communities_accepted: false, accepted_leaks_from_underlay: vec![], + allowed_anycast_prefixes: vec![], }, ), ( @@ -314,6 +315,7 @@ impl TestEnvOverrides { leak_tenant_host_routes_to_underlay: false, tenant_leak_communities_accepted: false, accepted_leaks_from_underlay: vec![], + allowed_anycast_prefixes: vec![], }, ), ]), diff --git a/crates/api/src/tests/machine_network.rs b/crates/api/src/tests/machine_network.rs index b954cadaee..f68ceb1ae6 100644 --- a/crates/api/src/tests/machine_network.rs +++ b/crates/api/src/tests/machine_network.rs @@ -107,11 +107,13 @@ async fn test_managed_host_network_config_with_sitewide_bgp_password(pool: sqlx: } #[crate::sqlx_test] -async fn test_managed_host_network_config_includes_routing_profile_accepted_leaks( +async fn test_managed_host_network_config_includes_routing_profile_prefix_lists( pool: sqlx::PgPool, ) { let profile_type = "ROUTE_LEAK_TEST"; let expected_leaks = vec!["10.42.0.0/24".to_string(), "2001:db8:42::/64".to_string()]; + let expected_allowed_anycast_prefixes = + vec!["192.0.2.0/24".to_string(), "2001:db8:99::/64".to_string()]; // Configure an FNN routing profile with explicit accepted underlay leaks. let env = api_fixtures::create_test_env_with_overrides( @@ -131,6 +133,12 @@ async fn test_managed_host_network_config_includes_routing_profile_accepted_leak prefix: prefix.parse().unwrap(), }) .collect(), + allowed_anycast_prefixes: expected_allowed_anycast_prefixes + .iter() + .map(|prefix| PrefixFilterPolicyEntry { + prefix: prefix.parse().unwrap(), + }) + .collect(), ..Default::default() }, )]), @@ -196,6 +204,17 @@ async fn test_managed_host_network_config_includes_routing_profile_accepted_leak .map(|leak| leak.prefix) .collect(); assert_eq!(actual_leaks, expected_leaks); + + // Verify anycast prefixes are preserved in the gRPC response. + let actual_allowed_anycast_prefixes: Vec<_> = routing_profile + .allowed_anycast_prefixes + .into_iter() + .map(|prefix| prefix.prefix) + .collect(); + assert_eq!( + actual_allowed_anycast_prefixes, + expected_allowed_anycast_prefixes + ); } #[crate::sqlx_test] diff --git a/crates/api/src/tests/vpc.rs b/crates/api/src/tests/vpc.rs index d99919bf86..d6b6c42193 100644 --- a/crates/api/src/tests/vpc.rs +++ b/crates/api/src/tests/vpc.rs @@ -121,6 +121,7 @@ async fn create_vpc(pool: sqlx::PgPool) -> Result<(), Box tenant_leak_communities_accepted: false, access_tier: 1, accepted_leaks_from_underlay: vec![], + allowed_anycast_prefixes: vec![], }, ), ( @@ -134,6 +135,7 @@ async fn create_vpc(pool: sqlx::PgPool) -> Result<(), Box tenant_leak_communities_accepted: false, access_tier: 0, accepted_leaks_from_underlay: vec![], + allowed_anycast_prefixes: vec![], }, ), ]), diff --git a/crates/rpc/proto/forge.proto b/crates/rpc/proto/forge.proto index 5b1783c71a..ffe9f81761 100644 --- a/crates/rpc/proto/forge.proto +++ b/crates/rpc/proto/forge.proto @@ -7712,6 +7712,10 @@ message RoutingProfile { // These are purely for routing purposes and will not have any // impact on ACLs. repeated PrefixFilterPolicyEntry accepted_leaks_from_underlay = 6; + + // IP prefixes that tenant hosts are allowed to announce to the DPU as + // anycast routes. Accepts IPv4 and IPv6 CIDR prefixes. + repeated PrefixFilterPolicyEntry allowed_anycast_prefixes = 7; } // ============================================================================