Symptoms
- A new spoke VCN can't reach on-premises, or on-premises can't reach it.
- Spoke-to-spoke traffic works but bypasses the hub firewall (or is dropped by it).
- Routes appear in one DRG route table but not another.
How DRG routing works
Every DRG attachment (VCN, virtual circuit, IPSec tunnel, remote peering) is associated with one DRG route table. That table decides where traffic arriving from that attachment goes.
A DRG route table gets its routes from:
- Static rules you add.
- Dynamic routes imported through an import route distribution: a list of statements like "import routes from all VCN attachments" or "from attachment X".
The default setup is simple and permissive: VCN attachments use one auto-generated table and other attachments another, each importing from the other side. Hub-and-spoke designs replace this with custom tables and distributions.
Traffic from attachment A ──► DRG route table of A ──► route to attachment B
(static rules + routes imported via its import distribution)
Step 1: list attachments and their tables
DRG=ocid1.drg.oc1.eu-frankfurt-1.aaaa...
oci network drg-attachment list --drg-id $DRG --all \
--query 'data[].{name:"display-name",type:"network-details".type,id:id,drgRt:"drg-route-table-id"}' --output table
oci network drg-route-table list --drg-id $DRG --all \
--query 'data[].{name:"display-name",id:id,importDist:"import-drg-route-distribution-id",ecmp:"is-ecmp-enabled"}' --output table
Write down, for the failing flow, the source attachment, its DRG route table and that table's import distribution.
Step 2: look at the routes in that table
oci network drg-route-table list-drg-route-rules --drg-route-table-id <drg-rt-ocid> --all \
--query 'data[].{dest:destination,type:"route-type",nextHop:"next-hop-drg-attachment-id",conflict:"is-conflict",blackhole:"is-blackhole"}' \
--output table
- Destination missing → nothing imported it, and no static rule.
- Next hop is the spoke itself, not the hub → traffic skips the firewall.
is-conflicttrue → several sources offer the same prefix; check which one won.
Step 3: check the import distribution
oci network drg-route-distribution-statement list --route-distribution-id <dist-ocid> --all \
--query 'data[].{prio:priority,action:action,match:"match-criteria"}'
Statements match by attachment type (VCN, VIRTUAL_CIRCUIT, IPSEC_TUNNEL, REMOTE_PEERING_CONNECTION), a specific attachment ID, or "match all". A new spoke isn't covered if the statement lists attachment IDs explicitly.
Add a statement:
oci network drg-route-distribution-statement add --route-distribution-id <dist-ocid> \
--statements '[{"action":"ACCEPT","priority":20,
"matchCriteria":[{"matchType":"DRG_ATTACHMENT_ID","drgAttachmentId":"<new-spoke-attachment>"}]}]'
Generate a JSON template with oci network drg-route-distribution-statement add --generate-param-json-input statements if the field names differ in your CLI version. Lower priority numbers are evaluated first.
Step 4: transit routing through the hub firewall
For "all traffic between spokes and on-premises goes through the hub firewall":
- Spoke attachments use a DRG route table with a static
0.0.0.0/0(or your summary prefixes) → hub VCN attachment. They should not import other spokes' routes directly. - Hub VCN attachment uses a DRG route table that imports routes from spokes and on-prem attachments, so traffic coming back from the firewall can reach everyone.
- On-prem attachments (virtual circuit, IPSec) use a table with static routes for spoke prefixes → hub VCN attachment.
- In the hub VCN, the VCN attachment has an ingress route table (a VCN route table set on the DRG attachment) that sends arriving traffic to the firewall's private IP.
- The firewall subnet's route table sends traffic for spokes and on-prem back to the DRG.
Set the hub's ingress route table on the attachment:
oci network drg-attachment update --drg-attachment-id <hub-attachment> \
--network-details '{"type":"VCN","routeTableId":"<hub-ingress-vcn-rt>"}'
And in that VCN route table:
oci network route-table update --rt-id <hub-ingress-vcn-rt> --force --route-rules '[
{"destination":"0.0.0.0/0","destinationType":"CIDR_BLOCK","networkEntityId":"<firewall-private-ip-ocid>"}
]'
Warning
Changing which DRG route table an attachment uses, or adding a default route, takes effect immediately for all traffic through that attachment. Change one spoke first, test, then roll out.
Step 5: check the VCN side too
Each spoke subnet's VCN route table needs routes for on-prem and other spokes pointing to the DRG. A perfect DRG configuration does nothing if the subnet sends traffic to a NAT gateway or has no route.
oci network route-table get --rt-id <spoke-subnet-rt> \
--query 'data."route-rules"[].{dest:destination,target:"network-entity-id"}' --output table
Decision table
| Symptom | Likely cause | Fix |
|---|---|---|
| New spoke unreachable from on-prem | Not in the on-prem attachment's table (no import / no static) | Add import statement or static route |
| Spoke-to-spoke skips firewall | Spoke tables import each other's routes | Use static default to hub; remove spoke imports |
| Traffic reaches firewall, replies lost | Firewall subnet route table lacks routes back to DRG | Add routes |
| Works for one prefix, not another | Conflict; a more specific route elsewhere | Check is-conflict and prefixes |
| On-prem learns nothing about new spoke | Export to virtual circuit/IPSec not covering it | Check export distribution and on-prem BGP |
Prevent it next time
- Draw the DRG design: each attachment, its DRG route table, its import distribution, and the hub ingress route table.
- Use attachment-type matching for spokes where possible, so new spokes are covered automatically.
- Manage DRG route tables and distributions in Terraform.
- Add a connectivity test (spoke ↔ spoke, spoke ↔ on-prem, via firewall logs) to every new spoke onboarding.