VPCs and Subnets
VPCs are private networks that isolate sets of instances from each other. Instances within a VPC subnet can talk to each other using private IP addresses (if firewall rules allow it) but traffic between VPCs must go through external IPs.
Every project comes with a default VPC. More VPCs can be set up by project administrators and collaborators to isolate the network traffic between different groups of VM instances. Each VPC includes a subnet by default with a RFC1918 IP address range. More subnets can be created for allocating different IP address ranges to different types of instances (e.g., databases versus web servers) and defining firewall rules that govern network traffic based on IP ranges. By default, all subnets within a VPC are able to route to one another.
External Connectivity
Instances that have one or more network interfaces get outbound internet access through NAT service on the rack. There are three classes of address which can be allocated to an instance:
External IP Type | Description | Allows Inbound External Traffic | Default | Maximum user-allocated |
---|---|---|---|---|
Source NAT | A shared temporary IP and port range. | No | Yes | N/A |
Ephemeral IP | A 1-to-1 temporary IP address. | Yes | No | 1 |
Floating IP | A 1-to-1 permanent IP address. | Yes | No | 32 |
Instances with network interfaces will always have a Source NAT (SNAT) address. These allow them to communicate with external hosts, but do not allow external hosts to initiate a session with an instance.
Ephemeral and floating IP addresses can be optionally attached to VM instances, which allow them to be accessible from hosts outside of their VPCs. These addresses can be attached at instance provisioning time and are detached when instances are terminated. Floating IPs may also be attached and detached from actively-running instances using the floating_ip_attach
and floating_ip_detach
endpoints. Ephemeral IPs are automatically assigned and released back into an IP pool when attached or detached, while floating IPs are explicitly created with a permanent identity in a project and allow greater control over which IP an instance should have.
An instance can have a maximum of 32 external IPs, and may have a single ephemeral IP.
Instances will receive traffic from external hosts on all of their ephemeral and floating IPs, and will transparently send reply traffic from the original external address. Outbound traffic will be mapped to an external IP by priority:
balanced(floating_ips) > ephemeral_ip
That is, an instance with two floating IPs 192.168.32.32
and 192.168.32.33
and an ephemeral IP 192.168.32.1
will randomly choose .32
or .33
as a source address for outbound traffic. It will never originate traffic on .1
unless the floating IPs are detached.
Firewall Rules
Firewall rules govern what traffic is allowed to be sent or received by an instance. The VPC firewall API allows you to define VPC-wide and per-instance policies for traffic according to its direction, protocol, and source/destination address.
Firewall Basics
Every network interface attached to an instance has a dedicated stateful firewall. By default, this firewall will:
Deny all inbound traffic.
Allow all outbound traffic.
Stateful refers to how the firewall behaves whenever a packet or flow is allowed. A decision to allow traffic will create a temporary exception for return-path traffic on the same network interface (i.e., with swapped source/destination ports and addresses).
We alter this behavior by installing firewall rules within the VPC. Each rule comprises:
A set of targets. These are specifiers that control which instances' firewalls a rule will be installed on. These include:
all instances in a VPC,
all instances in a VPC subnet,
all instances matching a specific IP address or CIDR,
a specific instance.
A set of filters. Filters determine which traffic a rule applies to. These include:
protocols — TCP, UDP, and ICMP,
ports — individual ports and/or ranges,
hosts — the IP address of a remote host, using similar specifiers to targets.
A direction. Inbound or Outbound.
An action. Allow or Deny.
A priority value, in the range
0—65535
.0
is the highest priority.
Each type of filter stores a list of possible values, and a packet matches a rule if it has an entry in every non-empty list of filter entries.
For example, a rule which contains the filters {"ports": [80, 443], "hosts": [{"type": "ip", "value": "192.168.1.1"}, {"type": "subnet", "value": "other-subnet"}]}
will match all packets satisfying (Port 80 OR Port 443) AND (IP 192.168.1.1 OR Subnet other-subnet)
.
When a packet arrives at an instance firewall, it will use the highest priority rule which both matches and is enabled. If several rules of identical priority match a packet, then deny will take precedence over any allow actions.
Default Rules
Whenever a VPC is created, it includes a few rules designed to minimize the configuration needed to get started:
Allow inbound SSH (TCP port 22) for all instances in the VPC,
Allow inbound ICMP (
ping
) for all instances in the VPC,Allow all inbound traffic from other instances in the VPC.
Configuration
Firewall rules may be set for a VPC using the vpc_firewall_rules_update
endpoint.
Here is an example of a rule that allows SSH from anywhere to all instances on the rack:
{
"action": "allow",
"description": "allow inbound TCP connections on port 22 from anywhere",
"direction": "inbound",
"filters": {
"hosts": null,
"ports": [
"22"
],
"protocols": [
"TCP"
]
},
"name": "allow-ssh",
"priority": 65534,
"status": "enabled",
"targets": [
{
"type": "vpc",
"value": "default"
}
],
"vpc_id": "f04c8e14-e4f8-4a1b-94db-cf34e8780738"
}
VPC Subnet Routing
VPC subnet routing controls how packets are forwarded from instances to the external network, and to other instances within the same VPC. The Oxide control plane allows you to configure this by creating routers. All subnets within a VPC share a System router, and may each have an optional Custom router that allows for basic user-controlled routing.
Routes within a router match traffic outbound from an instance, and direct it towards a known target. A route is selected using a longest-prefix match on a flow’s destination IP address, i.e., the 'most specific route' wins.
To offer a worked example, this guide assumes a VPC (subnet-guide
) containing three subnets with child instances:
kanto
(10.0.0.0/24),johto
(10.0.1.0/24),goldenrod
(10.0.1.5), andecruteak
(10.0.1.6),
hoenn
(192.168.0.0/24).mossdeep
(192.168.0.5)
Route semantics
Routes consist of two elements:
A destination, that controls which outbound packets will be affected by the route. Packets are matched on their destination IP address.
A target. This determines where a matched packet will be forwarded. This can resolve to an interface in the VPC, the external network, or an explicit drop.
Destinations and targets can refer to explicit IPs and prefixes, or to named resources like instances and subnets. If a named destination or target does not correspond to any entity in the VPC, the route will still be created. However, it will not be applied until a match can be made — i.e., an instance/subnet is created or renamed. A route will also become a no-op in the event that its named subnet or instance is deleted or renamed.
Type | Description | Example value | System-only |
---|---|---|---|
| Matches an individual IP address. |
| No |
| Matches an explicit IP subnet. |
| No |
| Matches both the IPv4 and IPv6 prefixes of a named subnet. |
| No |
| Matches all subnets of a named VPC. |
| Yes |
Type | Description | Example value | System-only |
---|---|---|---|
| Forwards packets to a VPC-local interface with a specific IP. |
| No |
| Forwards packets to the IPv4/IPv6 address of an instance’s primary NIC. |
| No |
| Forwards packets to the external network. |
| No |
| Drops all matched packets at the source. | N/A | No |
| Represents a route to every instance within a subnet. |
| Yes |
| Represents a route to every subnet within a VPC. |
| Yes |
System-only destinations and targets cannot be set or used in user-configured routes.
vpc
destinations and targets are placeholder types, as routes cannot be specified across VPC boundaries in the current product version.Instances do not yet support the use of IPv6.
System Routers
System routers are automatically created and managed by the control plane, and apply to all subnets within a VPC. Each VPC has a single system router, which will always be named "system". This router contains one route per subnet (which is immutable), and IPv4/v6 default outbound routes whose target can be changed.
We can inspect its routes using the vpc_router_route_list
endpoint, which will contain routes like the below:
Name | Type | Destination | Target | Mutable |
---|---|---|---|---|
kanto | VPC Subnet |
|
| No |
johto | VPC Subnet |
|
| No |
hoenn | VPC Subnet |
|
| No |
default-v4 | Default |
|
| Yes |
default-v6 | Default |
|
| Yes |
This means that:
All traffic directed to instances in any of the three subnets will have a valid route.
These rules match on the IPv4 and IPv6 prefixes of the named subnet.
Packets targeting an address in these subnets without a matching instance will be implicitly dropped.
Any IPv4 or IPv6 traffic which does not match one of the named subnets will be sent to the upstream network and leave the rack.
The target of the 'default-v4' and 'default-v6' routes may be modified using the vpc_router_route_update
endpoint.
Example 3: Controlling access to upstream networks shows how you can do so — and instead prevent a VPC from sending any outbound traffic, allow outbound traffic from a single subnet, or allow a subnet to reach only a fixed set of upstream addresses.
Custom Routers
Custom routers are collections of routes that are used to alter a subnet’s outbound routing behavior.
VPC subnets have an optional custom_router
field that can be used to assign a custom router.
Each network interface on an instance combines its VPC-wide system router with the custom router applied to its subnet into a single routing table.
A custom router may be used by many subnets.
Custom routes have higher priority than system routes when matching a destination subnet of identical prefix length.
Custom routers are not executed before the system router.
Let’s return to our worked example.
Suppose we want to drop traffic from hoenn
to both of the other subnets.
kanto
and johto
can obviously be aggregated into 10.0.0.0/23.
However, if we add a custom route to hoenn
specifying (10.0.0.0/23 → drop)
, then it will not take effect.
Both existing entries in the system router are more specific, matching on /24, and are chosen instead.
The correct solution is to create one drop rule per subnet — (subnet:kanto → drop)
and (subnet:johto → drop)
.
See Example 1: Creating/assigning routers and routes.
Example 1: Creating/assigning routers and routes
How would we configure hoenn
such that it cannot reach kanto
or johto
?
Custom routers allow for coarse filtering on instances' outbound traffic, and can be used as a complementary tool to firewall rules when per-instance exceptions aren’t required.
By default, our project contains a fully populated system router:
oxide vpc router list --project oxdoc --vpc subnet-guide
oxide vpc router route list --project oxdoc --vpc subnet-guide --router system
Command output
[
{
"description": "Routes are automatically added to this router as vpc subnets are created",
"id": "19c98bcf-4beb-434b-8ad1-32f9e6acb26f",
"kind": "system",
"name": "system",
"time_created": "2024-07-04T13:11:12.809802Z",
"time_modified": "2024-07-04T13:11:12.809802Z",
"vpc_id": "00345e31-8b2f-4c45-8adf-3055fba77efa"
}
]
[
{
"description": "The default route of a vpc",
"destination": {
"type": "ip_net",
"value": "0.0.0.0/0"
},
"id": "2ff59f00-a97b-46fc-a43d-1eb4ff010c8b",
"kind": "default",
"name": "default-v4",
"target": {
"type": "internet_gateway",
"value": "default"
},
"time_created": "2024-07-04T13:11:12.869085Z",
"time_modified": "2024-07-04T13:11:12.869085Z",
"vpc_router_id": "19c98bcf-4beb-434b-8ad1-32f9e6acb26f"
}, {
"description": "The default route of a vpc",
"destination": {
"type": "ip_net",
"value": "::/0"
},
"id": "564dfdaa-81c8-47d0-90b0-dd81fcbf31f8",
"kind": "default",
"name": "default-v6",
"target": {
"type": "internet_gateway",
"value": "default"
},
"time_created": "2024-07-04T13:11:12.930060Z",
"time_modified": "2024-07-04T13:11:12.930060Z",
"vpc_router_id": "19c98bcf-4beb-434b-8ad1-32f9e6acb26f"
}, {
"description": "VPC Subnet route for 'hoenn'",
"destination": {
"type": "subnet",
"value": "hoenn"
},
"id": "9b616956-4a9f-41bc-8db7-6bdcc7bc5a26",
"kind": "vpc_subnet",
"name": "hoenn",
"target": {
"type": "subnet",
"value": "hoenn"
},
"time_created": "2024-07-04T13:12:08.156555Z",
"time_modified": "2024-07-04T13:12:08.156555Z",
"vpc_router_id": "19c98bcf-4beb-434b-8ad1-32f9e6acb26f"
}, {
"description": "VPC Subnet route for 'johto'",
"destination": {
"type": "subnet",
"value": "johto"
},
"id": "0f72a784-4e2c-4e4a-8090-36cc56b2fa8d",
"kind": "vpc_subnet",
"name": "johto",
"target": {
"type": "subnet",
"value": "johto"
},
"time_created": "2024-07-04T13:11:51.701381Z",
"time_modified": "2024-07-04T13:11:51.701381Z",
"vpc_router_id": "19c98bcf-4beb-434b-8ad1-32f9e6acb26f"
}, {
"description": "VPC Subnet route for 'kanto'",
"destination": {
"type": "subnet",
"value": "kanto"
},
"id": "c0f3d46a-2a9b-46bb-9cd5-c87c2f85d727",
"kind": "vpc_subnet",
"name": "kanto",
"target": {
"type": "subnet",
"value": "kanto"
},
"time_created": "2024-07-04T13:11:40.867186Z",
"time_modified": "2024-07-04T13:11:40.867186Z",
"vpc_router_id": "19c98bcf-4beb-434b-8ad1-32f9e6acb26f"
}
]
We first create a new custom router:
oxide vpc router create \
--project oxdoc \
--vpc subnet-guide \
--name island-routes \
--description "It's hard to get around when there's too much water."
Command output
{
"description": "It's hard to get around when there's too much water.",
"id": "d39c3426-c183-45df-ac2c-cbbea0cec859",
"kind": "custom",
"name": "island-routes",
"time_created": "2024-07-04T13:48:41.512717Z",
"time_modified": "2024-07-04T13:48:41.512717Z",
"vpc_id": "00345e31-8b2f-4c45-8adf-3055fba77efa"
}
Note that this router has "kind": "custom"
.
We can then attach it to the hoenn
subnet:
oxide vpc subnet update \
--project oxdoc \
--vpc subnet-guide \
--subnet hoenn \
--custom-router island-routes
Command output
{
"custom_router_id": "d39c3426-c183-45df-ac2c-cbbea0cec859",
"description": "",
"id": "d144e4ed-c569-4655-8062-9623c686db63",
"ipv4_block": "192.168.0.0/24",
"ipv6_block": "fd98:db4a:93b:5ce9::/64",
"name": "hoenn",
"time_created": "2024-07-04T13:12:08.100819Z",
"time_modified": "2024-07-04T13:52:01.679857Z",
"vpc_id": "00345e31-8b2f-4c45-8adf-3055fba77efa"
}
Finally, we insert routes to override the system router:
Route definitions
{
"name": "ocean-1",
"description": "A large stretch of water.",
"destination": {"type": "subnet", "value": "kanto"},
"target": {"type": "drop"}
}
{
"name": "ocean-2",
"description": "An ocean too far.",
"destination": {"type": "subnet", "value": "johto"},
"target": {"type": "drop"}
}
oxide vpc router route create \
--project oxdoc \
--vpc subnet-guide \
--router island-routes \
--json-body ocean-1.json
oxide vpc router route create \
--project oxdoc \
--vpc subnet-guide \
--router island-routes \
--json-body ocean-2.json
We can verify by attempting to ping
between two instances: from mossdeep
(192.168.0.5) to goldenrod
(10.0.1.5).
No packets from mossdeep
(hoenn
) will arrive at goldenrod
(johto
).
ubuntu@mossdeep:~$ ping 10.0.1.5 -c 10
PING 10.0.1.5 (10.0.1.5) 56(84) bytes of data.
--- 10.0.1.5 ping statistics ---
10 packets transmitted, 0 received, 100% packet loss, time 9227ms
# ...
ubuntu@goldenrod:~$ sudo tcpdump -i enp0s8 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp0s8, link-type EN10MB (Ethernet), snapshot length 262144 bytes
^C
ubuntu@goldenrod:~$
goldenrod
to mossdeep
, as we have only installed a custom router on hoenn
.Example 2: Detaching custom routers
To detach island-routes
from hoenn
, we update the subnet via vpc_subnet_update
, leaving the --custom-router
field unset:
oxide vpc subnet update \
--project oxdoc \
--vpc subnet-guide \
--subnet hoenn
Command output
{
"description": "",
"id": "d144e4ed-c569-4655-8062-9623c686db63",
"ipv4_block": "192.168.0.0/24",
"ipv6_block": "fd98:db4a:93b:5ce9::/64",
"name": "hoenn",
"time_created": "2024-07-04T13:12:08.100819Z",
"time_modified": "2024-07-04T15:13:47.390091Z",
"vpc_id": "00345e31-8b2f-4c45-8adf-3055fba77efa"
}
Example 3: Controlling access to upstream networks
In a system router, we are able to modify the target of the 'default-v4' and 'default-v6' routes. We can use this to prevent outbound access to the wider Internet:
{
"destination": {"type": "ip_net", "value": "0.0.0.0/0"},
"target": {"type": "drop"}
}
oxide vpc router route update \
--project oxdoc \
--vpc subnet-guide \
--router system \
--route default-v4 \
--json-body isolate.json
Command output
{
"description": "The default route of a vpc",
"destination": {
"type": "ip_net",
"value": "0.0.0.0/0"
},
"id": "2ff59f00-a97b-46fc-a43d-1eb4ff010c8b",
"kind": "default",
"name": "default-v4",
"target": {
"type": "drop"
},
"time_created": "2024-07-04T13:11:12.869085Z",
"time_modified": "2024-07-04T15:43:50.670279Z",
"vpc_router_id": "19c98bcf-4beb-434b-8ad1-32f9e6acb26f"
}
A natural consequence is that we can no longer ping
or SSH into any of our instances!
We can selectively re-enable access, possibly on an arbitrary upstream prefix, by using a custom router:
{
"name": "hm02",
"description": "Fly!",
"destination": {"type": "ip_net", "value": "0.0.0.0/0"},
"target": {"type": "internet_gateway", "value": "default"}
}
oxide vpc router create \
--project oxdoc \
--vpc subnet-guide \
--name outbound-access \
--description "Leaving altogether!"
oxide vpc router route create \
--project oxdoc \
--vpc subnet-guide \
--router outbound-access \
--json-body open.json
oxide vpc subnet update \
--project oxdoc \
--vpc subnet-guide \
--subnet johto \
--custom-router outbound-access
We can now SSH into goldenrod
, but not mossdeep
.
It’s worth noting that 0.0.0.0/0
and ::/0
are not the only valid choices of destination for a custom internet_gateway
route — in custom routers, this target is compatible with all non-system destinations.
This can be used to limit outbound access to a specific upstream subnet, or to specific IP addresses.
0.0.0.0/0
or ::/0
to the drop
target.To restore the default system route’s state:
{
"destination": {"type": "ip_net", "value": "0.0.0.0/0"},
"target": {"type": "internet_gateway", "value": "default"}
}
oxide vpc router route update \
--project oxdoc \
--vpc subnet-guide \
--router system \
--route default-v4 \
--json-body restore.json
Example 4: Software routing & tunnels
Custom routes allow you to redirect entire subnets towards individual instances or internal IP addresses. This allows you to use an instance as a software router — either for local traffic processing, or to use it as a VPN tunnel endpoint (TEP).
Suppose we want goldenrod
to be responsible for all traffic between johto
and 172.30.0.0/22.
We can add a custom route to serve this function:
{
"name": "magnet-train",
"description": "No more ships for me. Next time, I'm taking the MAGNET TRAIN.",
"destination": {"type": "ip_net", "value": "172.30.0.0/22"},
"target": {"type": "instance", "value": "goldenrod"}
}
oxide vpc router route create \
--project oxdoc \
--vpc subnet-guide \
--router outbound-access \
--json-body magnet-train.json
However, goldenrod
will not yet receive any traffic bound for this subnet.
OPTE, which handles per-guest packet processing, will drop any traffic on an interface which does not match its assigned IP address.
This prevents instances from sending or receiving unwanted traffic.
To allow an instance to opt-in on a wider address range, we must set a list of transit IPs on the relevant NICs.
Stopped
.First, we need to fetch the name or ID of the primary NIC assigned to the instance.
oxide instance nic list --project oxdoc --instance goldenrod
Command output
[
{
"description": "",
"id": "7fb1d888-246a-47eb-b428-303cdcc1cfa2",
"instance_id": "29e9c92d-24b1-4513-897f-e18180aec568",
"ip": "10.0.1.5",
"mac": "A8:40:25:FE:64:16",
"name": "nic",
"primary": true,
"subnet_id": "f51f4097-f4eb-48f9-a595-a01b5f969f5e",
"time_created": "2024-07-04T14:32:47.243937Z",
"time_modified": "2024-07-04T14:32:47.243937Z",
"vpc_id": "00345e31-8b2f-4c45-8adf-3055fba77efa"
}
]
We can now update the network interface as needed:
{
"transit_ips": ["172.30.0.0/22"]
}
oxide instance nic update \
--interface 7fb1d888-246a-47eb-b428-303cdcc1cfa2 \
--json-body add-transit.json
Command output
{
"description": "",
"id": "7fb1d888-246a-47eb-b428-303cdcc1cfa2",
"instance_id": "29e9c92d-24b1-4513-897f-e18180aec568",
"ip": "10.0.1.5",
"mac": "A8:40:25:FE:64:16",
"name": "nic",
"primary": true,
"subnet_id": "f51f4097-f4eb-48f9-a595-a01b5f969f5e",
"time_created": "2024-07-04T14:32:47.243937Z",
"time_modified": "2024-07-05T11:09:05.972275Z",
"transit_ips": [
"172.30.0.0/22"
],
"vpc_id": "00345e31-8b2f-4c45-8adf-3055fba77efa"
}
If we attempt to contact an address in this range from ecruteak
, then we can see that goldenrod
will now receive all matching packets.
ubuntu@ecruteak:~$ ping 172.30.0.6
PING 172.30.0.6 (172.30.0.6) 56(84) bytes of data.
^C
--- 172.30.0.6 ping statistics ---
5 packets transmitted, 0 received, 100% packet loss, time 4078ms
# ...
ubuntu@goldenrod:~$ sudo tcpdump -i enp0s8 'dst 172.30.0.6'
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp0s8, link-type EN10MB (Ethernet), snapshot length 262144 bytes
11:42:37.207059 IP 10.0.1.6 > 172.30.0.6: ICMP echo request, id 1350, seq 1, length 64
11:42:38.233837 IP 10.0.1.6 > 172.30.0.6: ICMP echo request, id 1350, seq 2, length 64
11:42:39.257789 IP 10.0.1.6 > 172.30.0.6: ICMP echo request, id 1350, seq 3, length 64
11:42:40.281756 IP 10.0.1.6 > 172.30.0.6: ICMP echo request, id 1350, seq 4, length 64
11:42:41.305707 IP 10.0.1.6 > 172.30.0.6: ICMP echo request, id 1350, seq 5, length 64
^C
5 packets captured
5 packets received by filter
0 packets dropped by kernel
goldenrod
to 172.30.0.0/22 will be redirected back to itself.
This is fine if goldenrod
, as a TEP, is forwarding encapsulated packets towards another TEP on an IP outside of this range.
If this is not desired behavior, you can specify an instance within another subnet as a target.Internet Gateways
Internet gateway allows instances to access the internet from a VPC Subnet. Without a gateway, an instance cannot make outbound connections from the VPC subnet. Also, the gateway is intended to house the "pool" of external IP addresses (both Ephemeral and Floating) that can be used by instances to make those outbound connections. It is from the gateway that an instance derives its external IP address.
Default Gateway
In every VPC, there is a pair of system-generated entities that govern the default routing behavior:
an internet gateway named "default", linked to the default IP pool of the silo
a system router, using the default internet gateway for external traffic
oxide internet-gateway list --project oxdoc --vpc subnet-guide \
[
{
"description": "Automatically created default VPC gateway",
"id": "eb7f41b5-1e9b-46de-a096-33d129be0df3",
"name": "default",
"time_created": "2024-10-09T01:03:24.192557Z",
"time_modified": "2024-10-09T01:03:24.192557Z",
"vpc_id": "00407bc2-0560-4a83-a5f6-1af9de80ae53"
}
]
All subnets within a VPC share this default internet gateway.
Custom Gateway
Internet gateways can be used to ensure that instances only use certain external IP addresses when sending traffic to a given network. To override the default traffic flow with a different IP pool available to your silo, you can create a custom internet gateway and attach it to the desired IP pool or a single address in the pool, set it as the route target in a custom router, and attach the router to a VPC subnet. The custom internet gateway will be used for route determination only for instances with their primary NIC on that subnet.
Because all instances still have their Source NAT (SNAT) addresses allocated from your silo’s default IP pool, to leverage the custom internet gateway and route setup, you’ll need to attach ephemeral or floating IP addresses from an IP pool linked to the custom gateway.
Example
Here is an example that illustrates how to achieve custom routing for a specific IP address prefix:
We’ll start by making two different IP pools. If you are setting up a custom internet gateway in an existing silo, it is likely that there is already a default IP pool created for the silo and used by the default gateways.
oxide ip-pool create \
--name default \
--description 'default pool for all external traffic'
oxide ip-pool range add \
--pool default \
--first 172.20.27.101 \
--last 172.20.27.120
oxide ip-pool silo link \
--pool default \
--is-default true \
--silo training
oxide ip-pool create \
--name dc2 \
--description 'with routes to another system in datacenter 2'
oxide ip-pool range add \
--pool dc2 \
--first 172.20.27.151 \
--last 172.20.27.180
oxide ip-pool silo link \
--pool dc2 \
--is-default false \
--silo training
Next, create a custom internet gateway linked to the secondary IP pool
oxide internet-gateway create \
--project oxdoc \
--description 'dc2 gateway' \
--vpc subnet-guide \
--name dc2
oxide internet-gateway ip-pool attach \
--project oxdoc \
--vpc subnet-guide \
--gateway dc2 \
--ip-pool dc2 \
--name dc2-ig \
--description 'vpc dc2 gateway attachment'
Command output
{
"description": "dc2 gateway",
"id": "fce7ff27-ab4a-4cd9-8a61-85f2d40c7f8a",
"name": "dc2",
"time_created": "2024-10-09T23:59:13.193952Z",
"time_modified": "2024-10-09T23:59:13.193952Z",
"vpc_id": "00407bc2-0560-4a83-a5f6-1af9de80ae53"
}
{
"description": "vpc dc2 gateway attachment",
"id": "96e52462-9914-4afe-be21-4dd93dc6ef0a",
"internet_gateway_id": "fce7ff27-ab4a-4cd9-8a61-85f2d40c7f8a",
"ip_pool_id": "390523f5-ab8d-4dd6-b60f-2fcfb4df1c5c",
"name": "dc2-ig",
"time_created": "2024-10-10T00:00:25.617119Z",
"time_modified": "2024-10-10T00:00:25.617119Z"
}
Next, create a custom router that uses the internet gateway as the route target for traffic going to 45.154.216.0/24
.
oxide vpc router create \
--project oxdoc \
--vpc subnet-guide \
--description 'dc2 router' \
--name dc2-router
oxide vpc router route create \
--project oxdoc \
--vpc subnet-guide \
--router dc2-router \
--json-body igw-route.json
{
"name": "dc2",
"description": "route to dc2",
"target": {
"type": "internet_gateway",
"value": "dc2"
},
"destination": {
"type": "ip_net",
"value": "45.154.216.0/24"
}
}
Command output
{
"description": "dc2 router",
"id": "54bd5372-0ff2-402a-a0d2-58b415da2be1",
"kind": "custom",
"name": "dc2-router",
"time_created": "2024-10-10T00:17:20.339470Z",
"time_modified": "2024-10-10T00:17:20.339470Z",
"vpc_id": "00407bc2-0560-4a83-a5f6-1af9de80ae53"
}
{
"description": "route to dc2",
"destination": {
"type": "ip_net",
"value": "45.154.216.0/24"
},
"id": "7c6cfb97-e481-413f-8821-6e18b24a8a08",
"kind": "custom",
"name": "dc2",
"target": {
"type": "internet_gateway",
"value": "dc2"
},
"time_created": "2024-10-10T00:17:36.597609Z",
"time_modified": "2024-10-10T00:17:36.597609Z",
"vpc_router_id": "54bd5372-0ff2-402a-a0d2-58b415da2be1"
}
Finally, attach the custom router to the desired VPC subnet (in this case, we simply use the system-created "default" subnet)
oxide vpc subnet update \
--project oxdoc \
--vpc subnet-guide \
--subnet default \
--custom-router dc2-router
The set up is complete at this point. To make use of it, provision a floating IP from the "dc2" IP pool:
oxide floating-ip create \
--project oxdoc \
--name dc2-igw \
--description 'floating ip from dc2 gateway' \
--pool dc2
Then, create an instance with an ephemeral IP auto-assigned from the default pool and the "dc2-igw" floating IP (attached at create time or afterwards). Packets matching the 45.154.216.0/24
prefix will select any of the addresses in the "dc2" IP pool whereas other packets will select from the ephemeral IP and SNAT assigned to the instance.