DEV Community

Cover image for Your Cisco Intuition Might Fool You in AWS
Thiago Sagara for AWS Community Builders

Posted on Originally published at builder.aws.com AI-assisted

Your Cisco Intuition Might Fool You in AWS

The problem nobody warns you about

You spent years configuring Catalyst, Nexus, ISR, and, for the old-timers like me, a few 7200s. You know an ACL by heart, how many hosts fit in a /21, the relationship between a VLAN and a sub-interface, and even the hidden details in a show ip route output. Then you open the AWS console for the first time to build (or just look at) this thing called a VPC, and that strange feeling hits: it looks familiar (there's routing, there's subnets, there's even BGP), but nothing quite matches what you already know.

The most common mistake I see the cisco-boy make (because it's the one I made too) isn't assuming AWS is completely different. It's assuming it's similar enough that you don't need to check the details, and that blind spot is what got me the vast majority of the time.

This article isn't a Cisco-AWS glossary. It's a map I built to try to draw correlations, so that all my networking experience would get absorbed into AWS faster.


Leveling the playing field

You can't talk about VPC/Networking without understanding, even superficially, a few things happening "underneath" in AWS. This part isn't what matters day-to-day for troubleshooting, but it explains why the rules of the game changed.

There's no traditional Layer 2. No STP (thank God), no ARP broadcast flooding the network, no 802.1Q trunking. AWS's physical network (the underlay) is routed at Layer 3 from the ground up, running on a spine-leaf topology with no blocking path, which is one of the components that guarantees scale to millions of instances. In the reference video, the master Gustavo Santana explains this in detail: Arquitetura de Redes da AWS

The AWS Nitro System does the heavy lifting. Forget the idea of a traditional software hypervisor processing packets via CPU (like VMware or Hyper-V). Nitro is dedicated hardware (custom PCIe cards) that takes over network processing, encryption, and Security Group evaluation entirely off the host/instance CPU. That's why a modern EC2 delivers higher network throughput, reaching up to 600 Gbps on optimized Nitro instances. Ali Saidi and Filippo Sironi explain this in: Dive deep into the Nitro System

There's no path "learning" via dynamic protocol (not the way you're imagining). I'll tell you this one got me the first time I read about it. I figured AWS would use IS-IS, or BGP, for instance-to-instance communication. Instead, the source Nitro card queries a distributed database called the Mapping Service: "which physical host is this virtual destination IP on?" Once it gets the answer, it encapsulates the logical packet inside a physical underlay packet and dispatches it via unicast across the spine-leaf mesh. EC2 Nitro networking under the hood

There's even an internal AWS service called AWS Hyperplane, used in services like NAT Gateway, NLB, and PrivateLink, that does something a network person finds a bit "strange": it can connect two networks with identical CIDRs, abstracting away NAT along the path. (https://ngoyal16.medium.com/the-invisible-engine-how-aws-hyperplane-powers-privatelink-147c490d2ea9)

If you want to switch on bit-hacker mode and really understand how tenant isolation works at the hardware level, re:Invent 2025 had a dedicated session on how mathematical verification is done, via Isabelle/HOL, on Graviton5 instances. If you sometimes need to flip on the nerdy/curious side, like I do, it's worth a watch: (https://www.youtube.com/watch?v=hqqKi3E-oG8&t=3s)

Bottom line, and what I need you to hold onto: routing in AWS isn't discovered, it's declared. That's going to be the thread running through almost every broken assumption from here on.

Equivalences

Now, a quick-reference table. Keep in mind these are equivalences, meaning the goal is to let the concepts a network person already knows serve as a faster on-ramp into AWS.

AWS Concept Cisco Equivalent Where the Equivalence Can Bite You
Amazon VPC VRF + VDC Isolation (Nexus) The boundary is regional. There's no such thing as one VPC for North and South America. Even though it's entirely possible if you think in terms of VXLAN with L3VNI.
Subnet VLAN + L3 SVI A subnet is tied to a single AZ, and not to broadcast or ARP. Everything is unicast, controlled by the hypervisor (SDN).
Route table PBR + LPM Implicit, immutable local route. No dynamic routing by default, the route table is how you declare who the next-hop is. In practice it's not really a PBR, since it picks routes based on prefixes, but it also doesn't create an isolated routing table (we'll see this in the lab). Important detail: the route table you see here is the FIB, not the RIB.
Security Group stateful firewall (zone-based) SG Referencing: a rule points to another SG by ID, not by IP.
Network ACL stateless ACL No catch here. This one's a true, from-the-ground-up ACL.
Route 53 / VPC Resolver Standard DNS + forwarders No catch here either. And honestly, Route 53 is, without a doubt, AWS's best service.
Application Load Balancer F5 BigIP, or the late Cisco ACE The idea is to have no limits, so the tricks you'd pull with iRule, or even session-count limits on pools, simply didn't exist (until re:Invent 2025, when Target Optimizer launched).
Cloud WAN "Segment" VRF + MPLS + RD/RT Don't confuse this with Segment Routing. AWS makes it clear that here, "segment" really does mean VRF.
Hyperplane No equivalent. Maybe the ASIC Don't confuse Cisco APIC with AWS Hyperplane. The former works in the Control Plane, the latter in the Data Plane. That's why the ASIC cards might be a somewhat better equivalence (even though in practice the ones actually doing this work are the Nitro cards).
VPC Lattice No direct equivalent Too dense for one line. Deserves an article of its own.

Deep dives that wouldn't fit in a table

VPC and Subnets

Mapping a VPC to a VXLAN EVPN Multi-Site topology (or a Nexus fabric, for the old-timers) works as an isolation analogy, but always remember VPC carries geographic implications that a VRF or L3VNI, in theory, might not.

Another important point: if redundancy for you means a virtual PortChannel across different physical switches with servers spread out, forget it. In AWS, the first layer of redundancy is having different subnets, because in practice that means different data centers.

Route Tables

Yes, there's no dynamic protocol here, no OSPF, IS-IS, or EIGRP, but as a consolation prize the decision logic is Longest Prefix Match. In practice, if you forget to add the default route out through the NAT, the path just won't work, and I'll tell you something that I really missed: show ip route is a command that's hard to memorize, and on a large network it becomes nearly unusable.

I'm using PBR as the equivalence here, not VRF, because the VPC itself is closer to the VRF. You can have multiple route tables in a VPC, multiple subnets, and, using only local routing, guarantee that two different subnets can talk to each other. Picture the subnet as a VLAN interface on a core switch, but on it you have a PBR (the route table) deciding next-hop based on traffic source.

Bit-hacker mode on again for a curiosity: when you enable a Gateway Endpoint for S3 or DynamoDB, AWS automatically injects prefix-list-based routes directly into your route table, diverting that traffic onto the private backbone. It's the technical equivalent of a specific static route used to divert traffic.

ALB

Until Nov/25, the ALB concept had no limit on connection forwarding. You'd use round robin or least request (not least connections) to decide which target server got the connection. With the launch of Target Optimizer, that "limitation" started to exist, but it's not on the ALB, it's, as the name suggests, on the target.

In practice, an agent gets installed on the target, and it talks to the ALB over a control port, reporting the time and capacity it has available, and the ALB uses that to throttle the connection.

The important thing to remember here: there's still no equivalent to BigIP's "connection limits." So if your application isn't built for that, refactor before you bring it to AWS.

VPC Lattice

I initially thought about drawing a parallel between VPC Lattice and Cisco Service Mesh, or even IBN. But the first is focused on abstraction, the second is an extension of Istio, and the last one is more of a philosophy. So the best I can do here is say: think of VPC Lattice as a total abstraction of routing, and we'll talk about it properly in a dedicated article.

AWS's show ip route

Now let's talk about the network analyst's favorite command, tying together everything so far.

VPC is regionally scoped, so before anything else, make sure you're in the right region.

Already in the right region, and want to see the full "routing table" (what you'd do with a show ip route)?

Here it's just like Cisco. Since each route table is a VRF, you'd either run show ip route vrf <vrf> for each one, or show ip route vrf */all. In AWS, the command is a lot less friendly:

aws ec2 describe-route-tables --query "RouteTables[*].{RouteTableId: RouteTableId, VpcId: VpcId, Name: Tags[?Key=='Name'].Value[0], Routes: Routes[*].{Destination: DestinationCidrBlock, Target: GatewayId || TransitGatewayId || NatGatewayId || VpcPeeringConnectionId || InstanceId || LocalGatewayId, State: State}}" --output table
Enter fullscreen mode Exit fullscreen mode
awscommunitybuilder $ aws ec2 describe-route-tables --query "RouteTables[*].{RouteTableId: RouteTableId, VpcId: VpcId, Name: Tags[?Key=='Name'].Value[0], Routes: Routes[*].{Destination: DestinationCidrBlock, Target: GatewayId || TransitGatewayId || NatGatewayId || VpcPeeringConnectionId || InstanceId || LocalGatewayId, State: State}}" --output table
------------------------------------------------------
|                 DescribeRouteTables                |
+-------------------------+--------------------------+
|      RouteTableId       |          VpcId           |
+-------------------------+--------------------------+
|  rtb-0bd81ffd5d64586ec  |  vpc-0723f3eac9e93f538   |
+-------------------------+--------------------------+
||                      Routes                      ||
|+--------------------+--------------+--------------+|
||     Destination    |    State     |   Target     ||
|+--------------------+--------------+--------------+|
||  10.0.0.0/16       |  active      |  local       ||
|+--------------------+--------------+--------------+|
|                 DescribeRouteTables                |
+-------------------------+--------------------------+
|      RouteTableId       |          VpcId           |
+-------------------------+--------------------------+
|  rtb-0fa28d4f8ac142f35  |  vpc-0723f3eac9e93f538   |
+-------------------------+--------------------------+
||                      Routes                      ||
|+--------------+---------+-------------------------+|
||  Destination |  State  |         Target          ||
|+--------------+---------+-------------------------+|
||  10.0.0.0/16 |  active |  local                  ||
||  0.0.0.0/0   |  active |  igw-0dcda5a2bd336c5c6  ||
|+--------------+---------+-------------------------+|
awscommunitybuilder $ 
Enter fullscreen mode Exit fullscreen mode

And I also like the version with the route table's "name":

aws ec2 describe-route-tables --output json | jq -r '.RouteTables[] | .RouteTableId as $id | (.Tags[]? | select(.Key=="Name") | .Value) as $name | .VpcId as $vpc | .Routes[] | "\($id)\t\($name)\t\($vpc)\t\(.DestinationCidrBlock)\t\(.GatewayId // .TransitGatewayId // .NatGatewayId // .VpcPeeringConnectionId // .InstanceId // "local")\t\(.State)"' | column -t -s $'\t' | sed '1i RTB_ID NAME VPC_ID DESTINATION TARGET STATE'
Enter fullscreen mode Exit fullscreen mode
awscommunitybuilder $ aws ec2 describe-route-tables --output json | jq -r '.RouteTables[] | .RouteTableId as $id | (.Tags[]? | select(.Key=="Name") | .Value) as $name | .VpcId as $vpc | .Routes[] | "\($id)\t\($name)\t\($vpc)\t\(.DestinationCidrBlock)\t\(.GatewayId // .TransitGatewayId // .NatGatewayId // .VpcPeeringConnectionId // .InstanceId // "local")\t\(.State)"' | column -t -s $'\t' | sed '1i RTB_ID NAME VPC_ID DESTINATION TARGET STATE'
RTB_ID NAME VPC_ID DESTINATION TARGET STATE
rtb-0bd81ffd5d64586ec  network-fundamentals-private-rt  vpc-0723f3eac9e93f538  10.0.0.0/16  local                  active
rtb-0fa28d4f8ac142f35  network-fundamentals-public-rt   vpc-0723f3eac9e93f538  10.0.0.0/16  local                  active
rtb-0fa28d4f8ac142f35  network-fundamentals-public-rt   vpc-0723f3eac9e93f538  0.0.0.0/0    igw-0dcda5a2bd336c5c6  active
awscommunitybuilder $
Enter fullscreen mode Exit fullscreen mode

The reading is basically the same: to reach network 10.0.0.0/16, the outbound interface is local.


Lab

Here we're going to build the foundation for what will be the lab for the next articles. The CloudFormation is going to create a VPC, two route tables, two subnets, two instances on the public network and one on the private network (already with tcpdump, ping, and telnet installed).

The goal here is mainly to show how routing looks in AWS.

Clone the repo and run the CloudFormations:

git clone https://github.com/thiagosagara/awscommunitybuilder.git
cd awscommunitybuilder

aws cloudformation deploy \
  --template-file "AWS Network/basic/vpc-base.yaml" \
  --stack-name network-fundamentals-vpc \
  --parameter-overrides ProjectPrefix=network-fundamentals

aws cloudformation deploy \
  --template-file "AWS Network/basic/network-tools-instances.yaml" \
  --stack-name network-fundamentals-tools \
  --parameter-overrides ProjectPrefix=network-fundamentals \
  --capabilities CAPABILITY_IAM
Enter fullscreen mode Exit fullscreen mode

As I mentioned, the route table is closer to a PBR than a VRF, which is why we get connectivity between public and private "VLANs" by default.

Now let's create a loopback interface on the public-b instance, to show how routing behaves in AWS:

#Criando interface de loopback
sudo ip addr add 172.26.10.100/32 dev lo
sudo sysctl -w net.ipv4.ip_forward=1
sudo bash -c "echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-ip-forward.conf"

#habilitando o tcpdump:
sudo tcpdump -i any host 172.26.10.100 -nn &
Enter fullscreen mode Exit fullscreen mode

Note: for the lab to work, you need to disable source/destination check on the public-b instance:

Now, when we run ping 172.26.10.100 (with tcpdump running, same command) on the public-a instance, we'll notice the communication doesn't complete:

[ssm-user@ip-10-0-1-167 bin]$ ping 172.26.10.100
PING 172.26.10.100 (172.26.10.100) 56(84) bytes of data.
01:58:02.144363 ens5  Out IP 10.0.1.167 > 172.26.10.100: ICMP echo request, id 38715, seq 1, length 64
01:58:03.202312 ens5  Out IP 10.0.1.167 > 172.26.10.100: ICMP echo request, id 38715, seq 2, length 64
01:58:04.242365 ens5  Out IP 10.0.1.167 > 172.26.10.100: ICMP echo request, id 38715, seq 3, length 64
Enter fullscreen mode Exit fullscreen mode

Obviously, there's a missing route here, which we add with a simple:

sudo ip route add 172.26.0.0/16 via 10.0.1.237
Enter fullscreen mode Exit fullscreen mode

Since we don't want (and it isn't scalable) to add routes manually everywhere, we just add the route to the public route table instead (and delete the static route from the public-a instance with sudo ip route del 172.26.0.0/16):

aws ec2 create-route \
  --route-table-id rtb-049e28869ba16b8ff \
  --destination-cidr-block 172.26.0.0/16 \
  --instance-id i-0866fb5a8bdc93648
Enter fullscreen mode Exit fullscreen mode

What needs to be crystal clear to you here: the networking concepts didn't change. But if you picture the AWS network as a Cisco network, you're going to confuse yourself, and it can slow down your curve on absorbing the structure.


Conclusion

If your problem is cross-AZ latency troubleshooting, BGP behavior over Direct Connect, or multi-account architecture design with Transit Gateway, this article isn't enough. It gives you vocabulary and the most common breaking points, not operational depth.

That's what the next articles in this series solve. One of them takes a complete packet walk (from the HTTPS request all the way to the RDS, crossing the IGW, ALB, NACL, SG, Route Table) and turns it into a step-by-step troubleshooting methodology. After that, the AWS BGP series goes deep into what happens when you leave the world of declared static routing and enter real dynamic routing with Direct Connect and VPN.


Your networking intuition isn't wrong. It was just trained in a world where the path was discovered by protocol, and sometimes even packet by packet. In AWS, the path is declared by you, and that's the mental model shift that separates someone who just spins up a VPC from someone who actually knows how to run a network in production in the cloud.

transalate by AI

Top comments (0)