How Amazon VPC Lattice Routes Traffic at the Data Plane Level and Why This Makes Overlapping CIDRs a Non-Issue
This article explains how VPC lattice works at the networking level. What actually happens to a packet when it enters VPC Lattice? What address spaces are involved? Where does interception happen? And how does the data plane carry traffic between VPCs without involving VPC route tables?
Introduction
Understanding the mechanics is valuable on its own, but it also reveals why VPC Lattice natively solves one of the most painful AWS networking problems: connecting VPCs with overlapping CIDRs (the merger/acquisition scenario).
We'll start from the hardware, build up through the address spaces, and arrive at the complete packet flow.
Part 1: The Hardware Foundation — Nitro
Every Packet Passes Through the Nitro Card
Every modern EC2 instance runs on the AWS Nitro System. The Nitro System offloads networking, storage, and security functions from the main CPU onto purpose-built hardware: the Nitro Card for Networking.
This card sits between your instance's virtual network interface and the physical network. Every packet that enters or leaves your instance passes through it. This gives AWS a hardware interception point — a location where packets can be examined, redirected, or served locally without ever hitting the VPC's virtual network fabric or being evaluated by your route table.
The Nitro card is where security groups are enforced, where VPC flow logs are captured, and where certain traffic is redirected to AWS-managed services. It operates below the guest OS — your instance has no visibility into what happens at this layer.
The Interception Principle
AWS uses the Nitro card to serve infrastructure services to instances without requiring those services to have IP addresses in your VPC, without requiring route table entries (in some cases), and without consuming your network bandwidth to reach external endpoints. The mechanism: link-local addresses.
Part 2: The Address Spaces — How VPC Lattice Gets Its Own "Lane"
Link-Local Addresses: 169.254.0.0/16
RFC 3927 defines 169.254.0.0/16 as link-local — addresses that are only valid on the immediate network segment. They cannot be routed beyond a single link. In traditional networking, a device uses link-local when it has no DHCP server and self-assigns an IP.
AWS repurposed this concept for a different reason: link-local addresses can never conflict with any VPC CIDR (you cannot assign 169.254.0.0/16 to a VPC). This makes them the perfect address space for infrastructure services that need to be reachable from every instance regardless of the VPC's IP configuration.
When your instance sends a packet to any 169.254.x.x address, the Nitro card intercepts it at the hardware level. The packet never enters the VPC network fabric. It's served locally on the host or forwarded to an AWS backend over a dedicated internal channel.
AWS's Link-Local Service Map
AWS has carved out specific subnets within 169.254.0.0/16 for different infrastructure services:
| Address | Service | Behavior |
|---|---|---|
169.254.169.254 | Instance Metadata Service (IMDS) | Nitro card responds directly with instance metadata |
169.254.169.253 | VPC DNS Resolver (Route 53 Resolver) | Nitro card forwards to Route 53 |
169.254.169.123 | Amazon Time Sync Service (NTP) | Nitro card responds with time from local atomic clock |
169.254.170.2 | ECS Task Metadata Endpoint | Nitro card responds with task/container metadata |
169.254.171.0/24 | VPC Lattice Services | Nitro card forwards to VPC Lattice data plane |
These all follow the same pattern:
- Instance sends packet to
169.254.x.x - Nitro card intercepts — destination recognized as AWS infrastructure
- Packet handled locally or forwarded over internal channel
- Response delivered back to instance
This is why 169.254.169.254 (IMDS) works even in a VPC with no internet gateway, no NAT, no VPC endpoints. The traffic never leaves the host.
169.254.171.0/24: VPC Lattice's Dedicated Subnet
When AWS built VPC Lattice, they allocated 169.254.171.0/24 for service traffic. This /24 provides 254 usable addresses.
Critical detail: this is a shared, global range. It is NOT per-VPC, NOT per-account, NOT per-service. Every VPC Lattice service in every AWS account within a region shares these 254 IPs.
This seems impossibly limited. How can thousands of services fit in 254 addresses?
SNI Multiplexing: How 254 IPs Serve Unlimited Services
VPC Lattice services resolve to the same IP address (commonly 169.254.171.0). Multiple services — across different accounts — share this IP. Differentiation happens at the application layer through TLS SNI (Server Name Indication).
When your app connects to a VPC Lattice service:
- DNS resolves the service name to
169.254.171.0 - App opens a TLS connection to
169.254.171.0:443 - In the TLS ClientHello, the app includes the SNI header with the service's DNS name (e.g.,
svc-abc123.vpc-lattice-svcs.us-east-1.on.aws) - VPC Lattice reads the SNI and routes to the correct target group
This is the same technique that CDNs, reverse proxies, and hosting providers use to serve thousands of websites from a single IP address. The IP identifies the VPC Lattice data plane. The application-layer identifier (SNI for TLS, Host header for HTTP) identifies the specific service.
VPC Lattice supports three listener protocols: HTTP (plaintext), HTTPS (TLS terminated at VPC Lattice), and TLS (passthrough). For HTTPS/TLS, it reads the SNI in the TLS ClientHello. For plaintext HTTP, it reads the Host header. Both achieve the same multiplexing — multiple services share the same IP, differentiated at the application layer.
Note on traffic limits: AWS documents a 1024 packets per second aggregate limit for link-local infrastructure services (IMDS, DNS, NTP, Windows licensing). This limit applies to those specific services. VPC Lattice traffic, while using the link-local range 169.254.171.0/24, is handled by a dedicated data plane path and is NOT subject to this same 1024 PPS cap — VPC Lattice supports high-throughput application traffic (hundreds of thousands of requests per second) through its own bandwidth allocation.
How 169.254.171.0/24 Gets Into Your Route Table
Unlike IMDS (169.254.169.254), which is intercepted purely at the Nitro firmware level, VPC Lattice traffic requires a route table entry. When you associate a VPC with a service network, AWS adds a route:
Destination Target
────────────────────────── ──────────────────────────
pl-abcxyz (VPC Lattice prefix) vpc-lattice-sn-association
10.0.0.0/16 local
0.0.0.0/0 nat-gw
The managed prefix list (com.amazonaws.<region>.vpc-lattice) contains 169.254.171.0/24 (and 129.224.0.0/17 for resources). This route ensures the packet is handed to VPC Lattice's data plane rather than falling through to the default route.
Why a route table entry and not just Nitro interception? Because VPC Lattice also requires security group and NACL evaluation. Unlike IMDS/DNS/NTP which bypass SGs, VPC Lattice traffic must pass through your security groups (you need to allow outbound to 169.254.171.0/24). The route table entry keeps the traffic within the normal VPC networking path long enough for SGs/NACLs to be applied, before handing it to the data plane.
The Second Address Range: 129.224.0.0/17
For VPC Lattice resources (TCP targets like databases) accessed via a service network association, AWS uses 129.224.0.0/17 — a block of AWS-owned public address space repurposed as non-routable within VPC Lattice.
Why not 169.254.171.0/24 for resources too? Because resources need per-resource unique IPs (no TLS SNI for raw TCP connections to databases), and 254 addresses isn't enough. The /17 provides 32,766 addresses.
Each resource associated with a service network gets a unique IP from this pool. Traffic to that IP is intercepted via the same managed prefix list route.
The Third Mechanism: VPC-Local ENIs (Resource Endpoints)
For direct resource access — especially the overlapping CIDR pattern — VPC Lattice uses a completely different approach: a standard Elastic Network Interface with a regular VPC-local IP.
A Resource Endpoint (VPC endpoint type: resource) creates an ENI in your subnet, gets an IP from your CIDR (e.g., 10.0.99.5), and traffic arrives at it via normal local routing. The interception happens at the ENI ownership level: the Nitro networking stack recognizes the ENI is owned by VPC Lattice and hands the packet to the data plane instead of delivering it to compute.
This is the mechanism that enables overlapping CIDR connectivity — because the consumer VPC only addresses a local IP within its own CIDR. No special prefix list. No link-local address. Just a local ENI that happens to be a "trap door" into VPC Lattice.
| Access Method | IP Used | Interception | Protocol Support | Scale |
|---|---|---|---|---|
| Service network (services) | 169.254.171.0/24 | Route → Nitro → data plane | TLS only (SNI) | Unlimited via SNI |
| Service network (resources) | 129.224.0.0/17 | Route → Nitro → data plane | TCP (any) | 32K per region |
| Resource Endpoint | Your VPC subnet IP | ENI ownership → data plane | TCP (any) | Per-endpoint |
Part 3: The Backbone — What Happens Between VPCs
Once VPC Lattice's data plane has captured a packet (regardless of which on-ramp was used), it needs to deliver it to the destination. This happens over AWS's internal backbone network.
Identity-Based Routing, Not IP-Based
The internal transport doesn't use your VPC CIDRs for routing. It uses service identifiers and resource configuration IDs. The data plane knows:
- Which Resource Endpoint captured the packet
- Which Resource Configuration it maps to
- Which Resource Gateway should receive it
- What the target resource's IP/port is
This mapping is established at configuration time and stored in the data plane's forwarding tables. At runtime, the lookup is: endpoint → config → gateway → target. No IP-level routing between VPCs occurs.
Encapsulation
The original packet is encapsulated with internal headers containing the connection state, source endpoint identity, destination gateway, and target resource. This encapsulated packet travels over AWS's private backbone — the same infrastructure that carries S3 replication traffic, DynamoDB Global Tables data, and inter-AZ communication for managed services.
Why CIDRs Don't Matter at This Layer
Since routing between VPCs uses internal identifiers rather than source/destination IP evaluation, the actual CIDR addresses of the consumer and provider VPCs are completely irrelevant. Two VPCs with identical 10.0.0.0/16 ranges produce no conflict because their addresses are never compared or routed against each other at the backbone layer.
Part 4: The Resource Gateway — Delivery and Source NAT
When a packet arrives at the provider VPC, the Resource Gateway handles final delivery.
What the Resource Gateway Does
- Receives the packet from VPC Lattice's backbone at one of its ENIs (which has a local IP in the provider VPC, e.g.,
10.0.88.3) - Performs Source NAT — replaces the original source IP (consumer's app IP) with the gateway ENI's local IP
- Sets the destination to the target resource's IP (from the resource configuration)
- Puts the packet on the wire in the provider VPC as a normal local packet
Why Source NAT Is Essential
Without SNAT, the packet would arrive at the database with src: 10.0.1.10 (the consumer's app IP). The database would respond to 10.0.1.10, which routes locally in the provider VPC (it matches 10.0.0.0/16 → local). The response would go to the wrong place — a local IP 10.0.1.10 in the provider VPC (if it exists) rather than back through VPC Lattice to the consumer.
With SNAT to 10.0.88.3 (the gateway ENI), the database responds to 10.0.88.3. This arrives at the gateway ENI, where VPC Lattice captures it and carries it back through the backbone to the consumer.
Connection Capacity: 16 IPs × 55,000 Connections
Since every consumer connection gets SNAT'd to a gateway ENI IP, each IP can sustain ~55,000 concurrent TCP connections per destination (ephemeral port limitation). Default: 16 IPs per ENI across multiple AZs → millions of concurrent connections.
Part 5: Use Case — Overlapping CIDRs in M&A
Now that you understand the routing mechanics, the overlapping CIDR solution becomes obvious:
The Scenario
- Company A (acquirer): VPC
10.0.0.0/16, app at10.0.1.10 - Company B (acquired): VPC
10.0.0.0/16, database at10.0.2.50 - Company A needs to query Company B's database
Why It's Impossible with Traditional Networking
Company A's route table has 10.0.0.0/16 → local. A packet destined for 10.0.2.50 matches this local route and stays inside Company A's VPC. It can never reach Company B. VPC peering, Transit Gateway — both refuse to route between overlapping CIDRs.
Why It Works with VPC Lattice
With a Resource Endpoint in Company A and a Resource Gateway in Company B:
- DNS resolves the DB name to
10.0.99.5(Resource Endpoint ENI in Company A — LOCAL) - App sends to
10.0.99.5— matches10.0.0.0/16 → local(works fine, it IS local) - Nitro intercepts at the ENI — VPC Lattice-owned, handed to data plane
- Backbone carries packet using internal IDs (no CIDR evaluation)
- Resource Gateway in Company B delivers with SNAT to
10.0.88.3, destination10.0.2.50(both LOCAL) - Database responds to
10.0.88.3(local) → Gateway → Backbone → Endpoint → App
At no point does a packet route from one 10.0.0.0/16 to another. Each VPC only ever sees local traffic. VPC Lattice bridges them using identity-based routing on its internal backbone.
The Three-Segment Model
Segment 1 Segment 2 Segment 3
Consumer VPC AWS Backbone Provider VPC
App → Endpoint ENI Internal transport Gateway ENI → DB
10.0.1.10 → 10.0.99.5 (ID-based, no IPs) 10.0.88.3 → 10.0.2.50
Routing: local Routing: none Routing: local
CIDR: 10.0.0.0/16 CIDR: irrelevant CIDR: 10.0.0.0/16
Each segment operates independently in its own address space. The overlap exists only on paper — the two 10.0.0.0/16 ranges never interact.
Comparison: Overlapping CIDR Solutions
| VPC Lattice Resource Gateway | Private NAT GW + TGW | PrivateLink (Endpoint Service) | Custom Proxy | |
|---|---|---|---|---|
| Extra infrastructure | None | NAT GW + TGW + secondary CIDRs | NLB per service | EC2 + ASG |
| Setup complexity | Low (3 resources) | High (5+ resources) | Medium | High |
| Supports RDS by ARN | Yes (auto-failover) | No | No (need NLB) | No |
| Automatic Private DNS | Yes | No (manual Route 53) | Yes | No |
| Cross-account | AWS RAM | TGW sharing | Endpoint service | Complex |
| Protocol | TCP | Any | TCP (NLB) | Any |
| Bidirectional | No (add gateways both ways) | Yes | No | Yes |
| Maintenance | None (managed) | NAT rules, CIDR planning | NLB health checks | OS patching |
Key Takeaways
-
VPC Lattice uses three address mechanisms:
169.254.171.0/24(link-local, for L7 services via SNI),129.224.0.0/17(AWS-owned, for TCP resources via service networks), and VPC-local ENIs (for Resource Endpoints — the overlapping CIDR case). -
Interception happens at the Nitro level: Whether through a managed prefix list route or ENI ownership detection, packets are captured below the application layer and handed to VPC Lattice's data plane.
-
Backbone routing is identity-based, not IP-based: Internal transport uses Resource Configuration IDs and Gateway IDs, not source/destination VPC CIDRs. This is why overlapping addresses don't create conflicts.
-
Resource Gateways perform implicit SNAT: The gateway ENI's local IP becomes the source, ensuring the target resource responds locally and the response can be captured for the return path.
-
For overlapping CIDRs, the traffic never leaves the local address space in either VPC. Each side sees only local IPs. VPC Lattice bridges the gap without the two CIDR spaces ever intersecting.
References
- How VPC Lattice works
- Resource gateways in VPC Lattice
- Resource configurations for VPC resources
- Managing DNS resolution with Amazon VPC Lattice and VPC resources (AWS Blog)
- VPC resource gateways: Implementation patterns and use cases (AWS Blog)
- Exploring Amazon VPC Lattice (One Cloud Please)
- AWS Nitro System
- EC2 instance link-local addresses
- Language
- English
Relevant content
- Accepted Answer
asked 2 years ago
asked a year ago
AWS OFFICIALUpdated a year ago