Skip to main content

Simple Dedicated IP

Reserving a dedicated IP. Reading the ones a subscription holds, and where it may reserve one, is get_dedicated_ips; this page is the write side, reached through the sdip getter on the SDK Manager.

info

This is not the Dedicated IP module (dip), which drives the enclave-based protocol. Which one your product uses is decided by the subscription's entitlements.

The flow​

  1. Call getDedicatedIps. available lists the locations where the subscription may reserve; reserved what it holds.
  2. Call reserve with the id of an available location. When it returns, the address is held.
  3. Call getDedicatedIps again and connect through the new reserved entry's endpoints.

There is no release. Slots are freed by support or billing, never by the app.

Refreshing the locations and connecting to the reserved address​

reserve returns only the address and its location id. The endpoints to connect to come from getDedicatedIps, so after a successful reserve call it again for the same subscription. The SDK dropped its cached dataset as part of the reservation, so this call refetches and the new address is in reserved. Match it by ip, and connect through its endpoints exactly as you would with any location's endpoints (see Fetching endpoints for a location).

let assignment = try manager.sdip().reserve(subscriptionId: sub, locationId: location.id)

let refreshed = try manager.instanceDiscovery().getDedicatedIps(subscriptionId: sub)
switch refreshed.dedicatedIps {
case let .ready(available, reserved):
showReservationPicker(available)
showHeldAddresses(reserved)
if let held = reserved.first(where: { $0.ip == assignment.ip }) {
connect(to: held.endpoints)
}
case .failed:
// The address is reserved; the endpoints just could not be read right now. Keep what is on
// screen and call getDedicatedIps again after a minute.
keepPreviousAndRetryLater()
}

Two things to keep straight:

  • Re-read available as well as reserved. The location you reserved from now has one address fewer, and its availableCount may be zero.
  • A Failed answer here does not undo the reservation. It means the service that serves the endpoints could not be reached; the address is held and will appear once the call succeeds. Do not offer the reserve button again on Failed.

reserve​

ParameterDescription
subscription_idID of the subscription entitled to Simple Dedicated IP, see Obtaining a VPN Subscription ID
location_idThe id of a DedicatedIpLocation from getDedicatedIps().available. Not a ServerLocation id

Returns SdipReservation, throws SdipException. The server reserves the address at its upstream inside the request, so the call can take longer than most (the SDK allows 60 seconds); run it off the main thread.

Do not retry blindly; see Handling errors.

getAssignments​

ParameterDescription
subscription_idID of the subscription entitled to Simple Dedicated IP

Returns an array of SdipAssignment, oldest first, without endpoints. Empty for an entitled subscription with no claim.

SdipAssignment​

An enum with two cases:

CaseFieldsMeaning
Completedreservation: SdipReservationThe address is held
PendinglocationId: StringThe claim took a slot and the service is still fulfilling the address. Poll getAssignments until it turns Completed, typically within a couple of minutes

A Pending entry appears when a reserve call failed with TemporarilyUnavailable (a timeout, a lost response, a server error) while the server went on to complete the claim. It counts against the subscription's slots, so do not offer reserve for that location again.

SdipReservation​

NameTypeDescription
ipStringThe reserved address
locationIdStringMatches DedicatedIpLocation.id in getDedicatedIps

Handling errors​

Catch SdipException and branch on its variants; every variant carries the underlying SdkException as source, with the HTTP status and body, for logging. The three cases that need a distinct reaction are NotEntitled, NoCapacity and TemporarilyUnavailable.

do {
let assignment = try manager.sdip().reserve(subscriptionId: sub, locationId: location.id)
showReserved(assignment)
} catch SdipException.NotEntitled {
showUpsell()
} catch SdipException.SlotsExhausted {
showHeldAddressesOnly() // nothing frees a slot from the app
} catch SdipException.NoCapacity {
reloadLocationsAndAskAgain() // getDedicatedIps, then let the user pick another
} catch SdipException.InvalidLocation {
assertionFailure("only DedicatedIpLocation.id values are valid")
} catch SdipException.EntitlementEnded {
showUpsell()
} catch SdipException.TemporarilyUnavailable {
reconcileThenMaybeRetry() // see below
} catch let SdipException.ExceptionDetails(source) {
handleError(error: source)
}

TemporarilyUnavailable: reconcile before retrying​

TemporarilyUnavailable covers both a server-side error and a transport failure, timeouts included, so it is the one variant to handle here. A claim that failed on the way back may still complete on the server and occupy a slot. So never re-run reserve straight away. Call getAssignments first and look for that location:

  • Completed for that location: the reservation went through. Continue as after a success: refetch getDedicatedIps and connect. The SDK keeps its cached dataset on a failed reserve, so that refetch may still serve the previous dataset for up to an hour; the address is held either way.
  • Pending for that location: the server is still fulfilling it. Show it as in progress and poll getAssignments; do not call reserve again.
  • Nothing for that location: nothing is held, and reserve may be called again.
suspend fun reconcileThenMaybeRetry() {
val claim = manager.sdip().getAssignments(sub).firstOrNull {
when (it) {
is SdipAssignment.Completed -> it.reservation.locationId == location.id
is SdipAssignment.Pending -> it.locationId == location.id
}
}
when (claim) {
is SdipAssignment.Completed -> refreshAndConnect()
is SdipAssignment.Pending -> showInProgressAndPollLater()
null -> offerRetry()
}
}

getAssignments itself can throw TemporarilyUnavailable. In that case keep whatever is on screen, tell the user the service is unavailable, and let them try again later; do not fall through to another reserve.

Errors​

VariantMeaningWhat to do
NotEntitledThe subscription is not entitled to Simple Dedicated IPUpsell, or nothing
SlotsExhaustedEvery slot the entitlement grants is takenShow the held addresses
NoCapacityThe location sold out meanwhileRefetch getDedicatedIps, offer another
InvalidLocationNot a Simple Dedicated IP location idA bug: only available[].id values are valid
EntitlementEndedSubscription cancelled while in flightTreat as not entitled
TemporarilyUnavailableThe service did not complete the call: a server error, a timeout or another transport failureReconcile with getAssignments before retrying
ExceptionDetailsInvalid input, unattributed answerInspect source