diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 25b6f1ccfd..2af066cad0 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -1737,6 +1737,7 @@ async fn ip_pool_range_list( async fn ip_pool_range_add( rqctx: RequestContext>, path_params: Path, + // The plan is to change this back to shared::IpRange once IPv6 works range_params: TypedBody, ) -> Result, HttpError> { let apictx = &rqctx.context(); @@ -1744,6 +1745,8 @@ async fn ip_pool_range_add( let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let nexus = &apictx.nexus; let path = path_params.into_inner(); + // This is a bit of a hack to keep the service functions general with + // respect to IP version while disallowing IPv6 ranges at the API layer let range = shared::IpRange::V4(range_params.into_inner()); let pool_lookup = nexus.ip_pool_lookup(&opctx, &path.pool)?; let out = nexus.ip_pool_add_range(&opctx, &pool_lookup, &range).await?; @@ -1761,6 +1764,8 @@ async fn ip_pool_range_add( async fn ip_pool_range_remove( rqctx: RequestContext>, path_params: Path, + // Note that add is restricted to IPv4 while remove allows v4 or v6. This is + // meant as a safeguard in case existing systems have v6 ranges to remove. range_params: TypedBody, ) -> Result { let apictx = &rqctx.context(); @@ -1827,13 +1832,16 @@ async fn ip_pool_service_range_list( }] async fn ip_pool_service_range_add( rqctx: RequestContext>, - range_params: TypedBody, + // The plan is to change this back to shared::IpRange once IPv6 works + range_params: TypedBody, ) -> Result, HttpError> { let apictx = &rqctx.context(); let handler = async { let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let nexus = &apictx.nexus; - let range = range_params.into_inner(); + // This is a bit of a hack to keep the service functions general with + // respect to IP version while disallowing IPv6 ranges at the API layer + let range = shared::IpRange::V4(range_params.into_inner()); let out = nexus.ip_pool_service_add_range(&opctx, &range).await?; Ok(HttpResponseCreated(out.into())) }; @@ -1848,6 +1856,8 @@ async fn ip_pool_service_range_add( }] async fn ip_pool_service_range_remove( rqctx: RequestContext>, + // Note that add is restricted to IPv4 while remove allows v4 or v6. This is + // meant as a safeguard in case existing systems have v6 ranges to remove. range_params: TypedBody, ) -> Result { let apictx = &rqctx.context(); diff --git a/nexus/tests/integration_tests/ip_pools.rs b/nexus/tests/integration_tests/ip_pools.rs index f1d8825d0e..3cdb9a1cae 100644 --- a/nexus/tests/integration_tests/ip_pools.rs +++ b/nexus/tests/integration_tests/ip_pools.rs @@ -37,7 +37,6 @@ use nexus_types::external_api::params::IpPoolSiloUpdate; use nexus_types::external_api::params::IpPoolUpdate; use nexus_types::external_api::shared::IpRange; use nexus_types::external_api::shared::Ipv4Range; -use nexus_types::external_api::shared::Ipv6Range; use nexus_types::external_api::shared::SiloIdentityMode; use nexus_types::external_api::views::IpPool; use nexus_types::external_api::views::IpPoolRange; @@ -45,6 +44,7 @@ use nexus_types::external_api::views::IpPoolSiloLink; use nexus_types::external_api::views::Silo; use nexus_types::external_api::views::SiloIpPool; use nexus_types::identity::Resource; +use omicron_common::address::Ipv6Range; use omicron_common::api::external::IdentityMetadataUpdateParams; use omicron_common::api::external::NameOrId; use omicron_common::api::external::SimpleIdentity; @@ -853,59 +853,60 @@ async fn test_ip_pool_range_overlapping_ranges_fails( }; test_bad_ip_ranges(client, &ip_pool_add_range_url, &ipv4_range).await; + // TODO: put back IPv6 tests when IPv6 ranges are supported again // Test data for IPv6 ranges that should fail due to overlap - let ipv6_range = TestRange { - base_range: IpRange::V6( - Ipv6Range::new( - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 10), - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 20), - ) - .unwrap(), - ), - bad_ranges: vec![ - // The exact same range - IpRange::V6( - Ipv6Range::new( - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 10), - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 20), - ) - .unwrap(), - ), - // Overlaps below - IpRange::V6( - Ipv6Range::new( - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 5), - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 15), - ) - .unwrap(), - ), - // Overlaps above - IpRange::V6( - Ipv6Range::new( - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 15), - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 25), - ) - .unwrap(), - ), - // Contains the base range - IpRange::V6( - Ipv6Range::new( - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0), - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 100), - ) - .unwrap(), - ), - // Contained by the base range - IpRange::V6( - Ipv6Range::new( - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 12), - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 13), - ) - .unwrap(), - ), - ], - }; - test_bad_ip_ranges(client, &ip_pool_add_range_url, &ipv6_range).await; + // let ipv6_range = TestRange { + // base_range: IpRange::V6( + // Ipv6Range::new( + // std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 10), + // std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 20), + // ) + // .unwrap(), + // ), + // bad_ranges: vec![ + // // The exact same range + // IpRange::V6( + // Ipv6Range::new( + // std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 10), + // std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 20), + // ) + // .unwrap(), + // ), + // // Overlaps below + // IpRange::V6( + // Ipv6Range::new( + // std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 5), + // std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 15), + // ) + // .unwrap(), + // ), + // // Overlaps above + // IpRange::V6( + // Ipv6Range::new( + // std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 15), + // std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 25), + // ) + // .unwrap(), + // ), + // // Contains the base range + // IpRange::V6( + // Ipv6Range::new( + // std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0), + // std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 100), + // ) + // .unwrap(), + // ), + // // Contained by the base range + // IpRange::V6( + // Ipv6Range::new( + // std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 12), + // std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 13), + // ) + // .unwrap(), + // ), + // ], + // }; + // test_bad_ip_ranges(client, &ip_pool_add_range_url, &ipv6_range).await; } async fn test_bad_ip_ranges( @@ -952,6 +953,36 @@ async fn test_bad_ip_ranges( } } +#[nexus_test] +async fn test_ip_pool_range_rejects_v6(cptestctx: &ControlPlaneTestContext) { + let client = &cptestctx.external_client; + + create_ip_pool(client, "p0", None).await; + + let range = IpRange::V6( + Ipv6Range::new( + std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 10), + std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 20), + ) + .unwrap(), + ); + + let add_url = "/v1/system/ip-pools/p0/ranges/add"; + let error = + object_create_error(client, add_url, &range, StatusCode::BAD_REQUEST) + .await; + + let msg = "unable to parse JSON body: first: invalid IPv4 address syntax"; + assert!(error.message.starts_with(msg)); + + // same deal with service pool + let add_url = "/v1/system/ip-pools-service/ranges/add"; + let error = + object_create_error(client, add_url, &range, StatusCode::BAD_REQUEST) + .await; + assert!(error.message.starts_with(msg)); +} + #[nexus_test] async fn test_ip_pool_range_pagination(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; @@ -984,17 +1015,17 @@ async fn test_ip_pool_range_pagination(cptestctx: &ControlPlaneTestContext) { // address, which sorts all IPv4 before IPv6, then within protocol versions // by their first address. let ranges = [ - IpRange::V6( - Ipv6Range::new( - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 11), - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 20), + IpRange::V4( + Ipv4Range::new( + std::net::Ipv4Addr::new(10, 0, 0, 3), + std::net::Ipv4Addr::new(10, 0, 0, 4), ) .unwrap(), ), - IpRange::V6( - Ipv6Range::new( - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0), - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 10), + IpRange::V4( + Ipv4Range::new( + std::net::Ipv4Addr::new(10, 0, 0, 5), + std::net::Ipv4Addr::new(10, 0, 0, 6), ) .unwrap(), ), @@ -1262,15 +1293,15 @@ async fn test_ip_pool_service(cptestctx: &ControlPlaneTestContext) { let ranges = [ IpRange::V4( Ipv4Range::new( - std::net::Ipv4Addr::new(10, 0, 0, 1), - std::net::Ipv4Addr::new(10, 0, 0, 2), + std::net::Ipv4Addr::new(10, 0, 0, 3), + std::net::Ipv4Addr::new(10, 0, 0, 4), ) .unwrap(), ), - IpRange::V6( - Ipv6Range::new( - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0), - std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 10), + IpRange::V4( + Ipv4Range::new( + std::net::Ipv4Addr::new(10, 0, 0, 1), + std::net::Ipv4Addr::new(10, 0, 0, 2), ) .unwrap(), ), diff --git a/openapi/nexus.json b/openapi/nexus.json index 8469e17713..55a675f2bf 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -5560,7 +5560,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IpRange" + "$ref": "#/components/schemas/Ipv4Range" } } },