Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions nexus/src/app/ip_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use omicron_common::api::external::NameOrId;
use omicron_common::api::external::ResourceType;
use omicron_common::api::external::UpdateResult;
use ref_cast::RefCast;
use std::matches;
use uuid::Uuid;

/// Helper to make it easier to 404 on attempts to manipulate internal pools
Expand Down Expand Up @@ -291,6 +292,19 @@ impl super::Nexus {
return Err(not_found_from_lookup(pool_lookup));
}

// Disallow V6 ranges until IPv6 is fully supported by the networking
// subsystem. Instead of changing the API to reflect that (making this
// endpoint inconsistent with the rest) and changing it back when we
// add support, we accept them at the API layer and error here. It
// would be nice if we could do it in the datastore layer, but we'd
// have no way of creating IPv6 ranges for the purpose of testing IP
// pool utilization.
if matches!(range, IpRange::V6(_)) {
return Err(Error::invalid_request(
"IPv6 ranges are not allowed yet",
));
}

self.db_datastore.ip_pool_add_range(opctx, &authz_pool, range).await
}

Expand Down Expand Up @@ -347,6 +361,18 @@ impl super::Nexus {
let (authz_pool, ..) =
self.db_datastore.ip_pools_service_lookup(opctx).await?;
opctx.authorize(authz::Action::Modify, &authz_pool).await?;
// Disallow V6 ranges until IPv6 is fully supported by the networking
// subsystem. Instead of changing the API to reflect that (making this
// endpoint inconsistent with the rest) and changing it back when we
// add support, we accept them at the API layer and error here. It
// would be nice if we could do it in the datastore layer, but we'd
// have no way of creating IPv6 ranges for the purpose of testing IP
// pool utilization.
if matches!(range, IpRange::V6(_)) {
return Err(Error::invalid_request(
"IPv6 ranges are not allowed yet",
));
}
self.db_datastore.ip_pool_add_range(opctx, &authz_pool, range).await
}

Expand Down
4 changes: 4 additions & 0 deletions nexus/src/external_api/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1789,6 +1789,8 @@ async fn ip_pool_range_list(
}

/// Add range to IP pool
///
/// IPv6 ranges are not allowed yet.
#[endpoint {
method = POST,
path = "/v1/system/ip-pools/{pool}/ranges/add",
Expand Down Expand Up @@ -1880,6 +1882,8 @@ async fn ip_pool_service_range_list(
}

/// Add IP range to Oxide service pool
///
/// IPv6 ranges are not allowed yet.
#[endpoint {
method = POST,
path = "/v1/system/ip-pools-service/ranges/add",
Expand Down
151 changes: 76 additions & 75 deletions nexus/tests/integration_tests/ip_pools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use dropshot::HttpErrorResponseBody;
use dropshot::ResultsPage;
use http::method::Method;
use http::StatusCode;
use nexus_db_queries::authz;
use nexus_db_queries::context::OpContext;
use nexus_db_queries::db::datastore::SERVICE_IP_POOL_NAME;
use nexus_db_queries::db::fixed_data::silo::DEFAULT_SILO;
use nexus_db_queries::db::fixed_data::silo::INTERNAL_SILO_ID;
Expand Down Expand Up @@ -38,15 +40,16 @@ 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;
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::LookupType;
use omicron_common::api::external::NameOrId;
use omicron_common::api::external::SimpleIdentity;
use omicron_common::api::external::{IdentityMetadataCreateParams, Name};
Expand Down Expand Up @@ -765,7 +768,7 @@ async fn create_pool(client: &ClientTestContext, name: &str) -> IpPool {
async fn test_ip_pool_utilization_total(cptestctx: &ControlPlaneTestContext) {
let client = &cptestctx.external_client;

create_pool(client, "p0").await;
let pool = create_pool(client, "p0").await;

assert_ip_pool_utilization(client, "p0", 0, 0, 0, 0).await;

Expand All @@ -783,20 +786,36 @@ async fn test_ip_pool_utilization_total(cptestctx: &ControlPlaneTestContext) {

assert_ip_pool_utilization(client, "p0", 0, 5, 0, 0).await;

// now let's add a gigantic range just for fun
// Now let's add a gigantic range. This requires direct datastore
// shenanigans because adding IPv6 ranges through the API is currently not
// allowed. It's worth doing because we want this code to correctly handle
// IPv6 ranges when they are allowed again.

let nexus = &cptestctx.server.apictx().nexus;
let datastore = nexus.datastore();
let log = cptestctx.logctx.log.new(o!());
let opctx = OpContext::for_tests(log, datastore.clone());
let authz_pool = authz::IpPool::new(
authz::FLEET,
pool.identity.id,
LookupType::ByName("p0".to_string()),
);

let big_range = IpRange::V6(
Ipv6Range::new(
std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 1),
std::net::Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 0),
std::net::Ipv6Addr::new(
0xfd00, 0, 0, 0, 0xffff, 0xfff, 0xffff, 0xffff,
0xfd00, 0, 0, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
),
)
.unwrap(),
);
object_create::<IpRange, IpPoolRange>(client, &add_url, &big_range).await;
datastore
.ip_pool_add_range(&opctx, &authz_pool, &big_range)
.await
.expect("could not add range");

assert_ip_pool_utilization(client, "p0", 0, 5, 0, 18446480190918885375)
.await;
assert_ip_pool_utilization(client, "p0", 0, 5, 0, 2u128.pow(80)).await;
}

// Data for testing overlapping IP ranges
Expand Down Expand Up @@ -895,59 +914,9 @@ async fn test_ip_pool_range_overlapping_ranges_fails(
};
test_bad_ip_ranges(client, &ip_pool_add_range_url, &ipv4_range).await;

// 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;
// IPv6 tests removed along with support for IPv6 ranges in
// https://github.com/oxidecomputer/omicron/pull/5107
// Put them back when IPv6 ranges are supported again.
}

async fn test_bad_ip_ranges(
Expand Down Expand Up @@ -994,6 +963,38 @@ async fn test_bad_ip_ranges(
}
}

// Support for IPv6 ranges removed in
// https://github.com/oxidecomputer/omicron/pull/5107
// Delete this test when we support IPv6 again.
#[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;

assert_eq!(error.message, "IPv6 ranges are not allowed yet");

// 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_eq!(error.message, "IPv6 ranges are not allowed yet");
}

#[nexus_test]
async fn test_ip_pool_range_pagination(cptestctx: &ControlPlaneTestContext) {
let client = &cptestctx.external_client;
Expand Down Expand Up @@ -1026,17 +1027,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(),
),
Expand Down Expand Up @@ -1304,15 +1305,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(),
),
Expand Down
2 changes: 2 additions & 0 deletions openapi/nexus.json
Original file line number Diff line number Diff line change
Expand Up @@ -5432,6 +5432,7 @@
"system/networking"
],
"summary": "Add range to IP pool",
"description": "IPv6 ranges are not allowed yet.",
"operationId": "ip_pool_range_add",
"parameters": [
{
Expand Down Expand Up @@ -5847,6 +5848,7 @@
"system/networking"
],
"summary": "Add IP range to Oxide service pool",
"description": "IPv6 ranges are not allowed yet.",
"operationId": "ip_pool_service_range_add",
"requestBody": {
"content": {
Expand Down