Tuesday, June 11, 2013

Everything NAT

IOS has a plethora of NAT features that span from simple 1:1 NATs to policy NATs to basic round-robin load balancing. I've done a lot of NAT in my career, but most of it has been on an ASA.  Some of these features are not so obvious on IOS, and I've sometimes had a hard time producing specific functionality when I had to do anything beyond a basic NAT or PAT.  Here, I have deep-dived every NAT feature I can find, including use cases.  We will start with introducing the easy features, cover some directional issues, and then move on to advanced features.

Our lab is as follows:



The subnet between R1, R2, R3 and R4 will be 192.168.0.0/24, with the fourth octet being the router number.  The link between R4 and R5 will be 30.0.0.0/24 with the fourth octet being the router number. Each router will have a loopback of X.X.X.X where X is its router number (i.e. R3 = 3.3.3.3).  R4 will perform all the NATing.

Static 1:1 NAT
R1, R2, and R3 all have a default route pointing towards R4.  These will be our "inside".  R5 doesn't have a route for anything other than its own loopback and connected 30.0.0.0/24 connected segment.  This will be our "outside".

Let's get R1 and R5 talking to each other.

R4(config)#int fa0/1
R4(config-if)#ip nat inside
R4(config-if)#int fa0/0
R4(config-if)#ip nat outside
R4(config)#ip nat inside source static 192.168.0.1 30.0.0.20

R1#ping 30.0.0.5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 30.0.0.5, timeout is 2 seconds:
..!!!
Success rate is 60 percent (3/5), round-trip min/avg/max = 48/52/60 ms

R5#debug ip icmp
ICMP packet debugging is on
R5#
*Mar  1 00:26:35.911: ICMP: echo reply sent, src 30.0.0.5, dst 30.0.0.20
*Mar  1 00:26:37.899: ICMP: echo reply sent, src 30.0.0.5, dst 30.0.0.20
*Mar  1 00:26:37.979: ICMP: echo reply sent, src 30.0.0.5, dst 30.0.0.20
*Mar  1 00:26:38.027: ICMP: echo reply sent, src 30.0.0.5, dst 30.0.0.20

Really straightforward.  This flips the source address from 192.168.0.1 to 30.0.0.20 when moving from inside to outside.  From outside to inside the destination address will be flipped from 30.0.0.20 back to 192.168.0.1.  R4 will ARP for 30.0.0.20 on the outside, which we can see via the alias table:

R4(config)#do show ip alias
Address Type             IP Address      Port
Interface                4.4.4.4
Interface                30.0.0.4
Dynamic                  30.0.0.20
Interface                192.168.0.4

If for some reason we don't want R4 to ARP for 30.0.0.20, we could use no-alias:

R4(config)#ip nat inside source static 192.168.0.1 30.0.0.20 no-alias
R4(config)#do sh ip alias
Address Type             IP Address      Port
Interface                4.4.4.4
Interface                30.0.0.4
Interface                192.168.0.4

R5#clear arp
R5#ping 30.0.0.20
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 30.0.0.20, timeout is 2 seconds:
.....
Success rate is 0 percent (0/5)

Let's make R4 ARP again.

R4(config)#no ip nat inside source static 192.168.0.1 30.0.0.20 no-alias
R4(config)#ip nat inside source static 192.168.0.1 30.0.0.20

R5#ping 30.0.0.20
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 30.0.0.20, timeout is 2 seconds:
.!!!!
Success rate is 80 percent (4/5), round-trip min/avg/max = 28/54/80 ms

R1#debug ip icmp
ICMP packet debugging is on
R1#
*Mar  1 00:38:39.007: ICMP: echo reply sent, src 192.168.0.1, dst 30.0.0.5
*Mar  1 00:38:39.067: ICMP: echo reply sent, src 192.168.0.1, dst 30.0.0.5
*Mar  1 00:38:39.103: ICMP: echo reply sent, src 192.168.0.1, dst 30.0.0.5
*Mar  1 00:38:39.175: ICMP: echo reply sent, src 192.168.0.1, dst 30.0.0.5

You see the mapping is bidirectional, R5 can reach R1.

Let's create some more traffic and check out the NAT table.

R1#telnet 30.0.0.5
Trying 30.0.0.5 ... Open

Password required, but none set
[Connection to 30.0.0.5 closed by foreign host]

R4#sh ip nat translations
Pro Inside global      Inside local       Outside local      Outside global
tcp 30.0.0.20:62371    192.168.0.1:62371  30.0.0.5:23        30.0.0.5:23
--- 30.0.0.20          192.168.0.1        ---                ---
There's some interesting stuff here.  We see the entry created by our nat statement:
--- 30.0.0.20          192.168.0.1        ---                ---

We'll go over this more further down the document.  Let's focus on:
Pro Inside global      Inside local       Outside local      Outside global
tcp 30.0.0.20:62371    192.168.0.1:62371  30.0.0.5:23        30.0.0.5:23

Why is this here?  I thought this was NAT, not PAT, so we shouldn't need all these port numbers.  For that matter we don't even care about the outside local/global addresses, really.

This is because of a feature activated by ip nat create flow-entries.  This is a default-on feature to accelerate the NAT process.  If you want to disable it, you'd use:

R1#telnet 30.0.0.5
Trying 30.0.0.5 ... Open

Password required, but none set
[Connection to 30.0.0.5 closed by foreign host]

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
--- 30.0.0.20          192.168.0.1        ---                ---

That's more what you'd expect to see, even if it is slower.  I've now re-enabled ip nat create flow-entries.

Static PATs. 

First we'll remove our NAT.

R4(config)#no ip nat inside source static 192.168.0.1 30.0.0.20
R4(config)#ip nat inside source static tcp 192.168.0.1 19 30.0.0.20 5000

This should map port 19 (chargen) on the inside to port 5000 on the outside.

R1(config)#service tcp-small-servers   ! enable chargen on R1

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
tcp 30.0.0.20:5000     192.168.0.1:19     ---                ---

There's the translation from our static PAT.

We see that we can no longer telnet there:

R5#telnet 30.0.0.20
Trying 30.0.0.20 ...
% Connection refused by remote host

What about telnetting to port 5000?

R5#telnet 30.0.0.20 5000
Trying 30.0.0.20, 5000 ... Open
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh
"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi

Clearly, we can reach chargen on R1.

We've also added the expected entry in the NAT table:

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
tcp 30.0.0.20:5000     192.168.0.1:19     30.0.0.5:13368     30.0.0.5:13368
tcp 30.0.0.20:5000     192.168.0.1:19     ---                ---

What if we were translating some protocol that needed an ALG (Application Layer Gateway)?  Turns out IOS's NAT process has some fixups built-in for applications that contain IP and port information inside the packet.  This process happens be default.  If you want to disable it, you'd use:

ip nat inside source static tcp 192.168.0.1 19 30.0.0.20 5000 no-payload

Dynamic NAT

I've removed the static NAT/PAT config.

R4(config)#access-list 90 permit 192.168.0.0 0.0.0.255
R4(config)#ip nat pool nat-pool 30.0.0.50 30.0.0.70 netmask 255.255.255.0
R4(config)#ip nat inside source list 90 pool nat-pool

This will perform a 1:1 NAT translation, dynamically, for the first 20 hosts on 192.168.0.0/24 on to 30.0.0.50 through 70. 

R1#telnet 30.0.0.5
Trying 30.0.0.5 ... Open

Password required, but none set
[Connection to 30.0.0.5 closed by foreign host]

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
tcp 30.0.0.50:52584    192.168.0.1:52584  30.0.0.5:23        30.0.0.5:23
--- 30.0.0.50          192.168.0.1        ---                ---

We see that 192.168.0.1 has translated to 30.0.0.50 as expected.  Now that this is setup we'll see that dynamic NAT is actually reversible:

R5#telnet 30.0.0.50
Trying 30.0.0.50 ... Open

Password required, but none set
[Connection to 30.0.0.50 closed by foreign host]

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
tcp 30.0.0.50:23       192.168.0.1:23     30.0.0.5:63636     30.0.0.5:63636
tcp 30.0.0.50:52584    192.168.0.1:52584  30.0.0.5:23        30.0.0.5:23
--- 30.0.0.50          192.168.0.1        ---                ---

What about the other routers?

R2#ping 30.0.0.5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 30.0.0.5, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 32/55/96 ms

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
--- 30.0.0.50          192.168.0.1        ---                ---
icmp 30.0.0.51:1       192.168.0.2:1      30.0.0.5:1         30.0.0.5:1
--- 30.0.0.51          192.168.0.2        ---                ---

We now see the new dynamic mapping, 192.168.0.2 = 30.0.0.51. 

What if we wanted to do a bulk 1:1 NAT?

R4(config)#do clear ip nat trans *
R4(config)#no ip nat inside source list 90 pool nat-pool
R4(config)#ip nat inside source static network 192.168.0.0 30.0.0.0 /24

This will do a pretty clever thing, and match the fourth octet on a 1:1 basis when generating traffic from inside -> outside.  Outside -> inside is reversible after the inside->outside translation has taken place and is in the table.

R3#telnet 30.0.0.5
Trying 30.0.0.5 ... Open

Password required, but none set
[Connection to 30.0.0.5 closed by foreign host]

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
--- 30.0.0.3           192.168.0.3        ---                ---
--- 30.0.0.0           192.168.0.0        ---                ---

We see .3 = .3, as anticipated.

Let's convert the earlier example to dynamic PAT instead.

Dynamic PAT

R4(config)#ip nat inside source list 90 pool nat-pool overload

Now our "nat-pool" nat pool still references 20 IPs, which is unnecessary, this would work fine with one IP address.  But let's test anyway:

R1#telnet 30.0.0.5
Trying 30.0.0.5 ... Open

Password required, but none set
[Connection to 30.0.0.5 closed by foreign host]

R2#ping 30.0.0.5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 30.0.0.5, timeout is 2 seconds:
.!!!!
Success rate is 80 percent (4/5), round-trip min/avg/max = 36/45/56 ms

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
tcp 30.0.0.52:26904    192.168.0.1:26904  30.0.0.5:23        30.0.0.5:23
icmp 30.0.0.52:2       192.168.0.2:2      30.0.0.5:2         30.0.0.5:2

We see that both our sessions are now sourced dynamically off 30.0.0.52, instead of one IP per device.

You can also PAT to an interface:

R4(config)#no ip nat inside source list 90 pool nat-pool overload
R4(config)#ip nat inside source list 90 interface fa0/0

I ran the same telnet/ping from R1 and R2 here, not shown.

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
tcp 30.0.0.4:30046     192.168.0.1:30046  30.0.0.5:23        30.0.0.5:23
icmp 30.0.0.4:3        192.168.0.2:3      30.0.0.5:3         30.0.0.5:3

We see all the sessions coming off the interface IP, 30.0.0.4.  Note I did not use the overload command above.  I could've, but it's implied when you PAT off an interface in this fashion.

Let's say you want a catch-all host behind your PAT.  It would get all the traffic not going somewhere else.  This is similar to the "DMZ host" feature that's on a lot of economy routers.  Let's make R2 our catch-all:

ip nat inside source static 192.168.0.2 interface Fa0/0

Just to reconfirm that R1 can still reach from inside->outside:

R1#ping 30.0.0.5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 30.0.0.5, timeout is 2 seconds:
.!!!!
Success rate is 80 percent (4/5), round-trip min/avg/max = 36/46/68 ms

I've enabled local login on R2 (not shown here).

R5#telnet 30.0.0.4
Trying 30.0.0.4 ... Open

User Access Verification
Password:
R2>

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
tcp 30.0.0.4:23        192.168.0.2:23     30.0.0.5:52330     30.0.0.5:52330
--- 30.0.0.4           192.168.0.2        ---                ---

That covers all the basics, let's move on...

NAT Table & Order of Operations

So far we've been looking at "domain based" NAT.  "domains" meaning inside & outside.  As we've been seeing, the NAT table for domain-based NAT is viewed by show ip nat translations.  But we haven't examined what these entries mean too much.

We're actually going to cover four topics here:

- Examining the NAT table
- Eliminating routing by using NAT
- ip nat outside
- Nat Virtual Interface (NVI)

I've eliminated all the existing NAT configuration and we're starting from scratch.

I've also removed the default route on R1 that's pointing at R4:
R1(config)#no ip route 0.0.0.0 0.0.0.0 192.168.0.4

R4(config)#ip nat inside source static 192.168.0.1 30.0.0.50
R4(config)#ip nat outside source static 30.0.0.5 192.168.0.50

The idea is to have R1 perceive R5 as 192.168.0.50, and R5 to perceive R1 as 30.0.0.50.

Let's have a look at the NAT tables.



I personally don't care for the layout of the domain-based NAT table.  Here's the way I decipher it.

These are both source NATs.  The top one is created by ip nat outside, the bottom one is created by ip nat inside.  I'm going to generate a traffic flow so that we can see the outcome of this better.

(Note I've fixed something behind-the-scenes here so that I can demonstrate this point first.  We'll discuss later.)



I've created a ping on R1:
SOURCE: 192.168.0.1
DESTINATION: 192.168.0.50

R4 performed two NATs:
1) A source NAT from 192.168.0.1 to 30.0.0.50
2) A reverse source NAT of 192.168.0.50 to 30.0.0.5.  The source NAT is for the outside->inside direction (30.0.0.5 -> 192.168.0.50), and this is the "reversible" method we've been discussing.

That's all fine and dandy, but here's my quicky method for seeing what this all means:



If inside->outside, our pre-translation packet is the inside pair (inside local, outside local) or "1 -> 2" (192.168.0.1 -> 192.168.0.50) and our post-translation packet is the outside pair (inside global -> outside global) or "3 -> 4" (30.0.0.50 -> 30.0.0.5).

Outside -> Inside is exactly flipped:



Original packet is (Outside Global, Inside Global) or "1 -> 2" (30.0.0.5 -> 30.0.0.50); and our post-translation packet is the inside pair, reversed (Outside Local -> Inside Local) or "3 -> 4" (192.168.0.50 -> 192.168.0.1).

As such, we are now able to get by on translations and ARPs, no routing is required.... sort of.

I mentioned I'd "fixed"  something undisclosed above, let's look at what would have gone wrong here.  I removed the fix.

Disabling CEF so that we can debug the transit packets...

R4(config)#int fa0/0
R4(config-if)#no ip route-cache
R4(config-if)#int fa0/1
R4(config-if)#no ip route-cache
R4(config-if)#do debug ip packet
IP packet debugging is on
R4(config-if)#do debug ip nat
IP NAT debugging is on

R1#telnet 192.168.0.50
Trying 192.168.0.50 ...
% Connection refused by remote host

Clearly broken, what'd R4's debug have to say?

R4(config-if)#
*Mar  1 07:41:33.458: IP: tableid=0, s=192.168.0.1 (FastEthernet0/1), d=192.168.0.50 (FastEthernet0/1), routed via RIB
*Mar  1 07:41:33.458: IP: s=192.168.0.1 (FastEthernet0/1), d=192.168.0.50 (FastEthernet0/1), len 44, rcvd 3
*Mar  1 07:41:33.466: IP: tableid=0, s=192.168.0.50 (local), d=192.168.0.1 (FastEthernet0/1), routed via FIB
*Mar  1 07:41:33.466: IP: s=192.168.0.50 (local), d=192.168.0.1 (FastEthernet0/1), len 40, sending

The issue is on line 1.  We're routing from Fa0/1 to Fa0/1.  That's because even though R4 ARPed for 192.168.0.50, it sees the egress interface as the one it came in on. 

This is where order of operations comes in.  Inside->Outside and Outside->Inside NAT are handled differently.

Inside->Outside "routes first" and NATs second.  I put "routes first" in quotes, because it's more like "picks an interface first" (which I suppose is routing). Outside->Inside nat NATs first and "routes second". 

Problem is, the packet is basically deemed invalid before the NAT even happens.  We need a more specific route to fix this issue.  This is what I "fixed" earlier.

R4(config)#ip route 192.168.0.50 255.255.255.255 fa0/0

This /32 route will push traffic for 192.168.0.50 on to the outside interface.

R1#telnet 192.168.0.50
Trying 192.168.0.50 ... Open

Password required, but none set
[Connection to 192.168.0.50 closed by foreign host]

Now we can connect!

R4(config)#
*Mar  1 07:53:13.510: IP: tableid=0, s=192.168.0.1 (FastEthernet0/1), d=192.168.0.50 (FastEthernet0/0), routed via RIB
*Mar  1 07:53:13.510: NAT: s=192.168.0.1->30.0.0.50, d=192.168.0.50 [49922]
*Mar  1 07:53:13.514: NAT: s=30.0.0.50, d=192.168.0.50->30.0.0.5 [49922]
*Mar  1 07:53:13.514: IP: s=30.0.0.50 (FastEthernet0/1), d=30.0.0.5 (FastEthernet0/0), g=30.0.0.5, len 44, forward

And now we're seeing traffic going from Fa0/1 to Fa0/0, and then the two pre-discussed NATs happening.

A slightly cleaner way to make this happen:

R4(config)#no ip route 192.168.0.50 255.255.255.255 fa0/0
R4(config)#ip nat outside source static 30.0.0.5 192.168.0.50 add-route

The "add-route" command creates the static route towards 192.168.0.50 on Fa0/0 automatically:

R4(config)#do sh ip route static
     192.168.0.0/24 is variably subnetted, 2 subnets, 2 masks
S       192.168.0.50/32 [1/0] via 30.0.0.5

There's another method, as well.  NVI-based NAT.
I've read a lot of blogs saying NVI-based NAT is the "new NAT method".  I don't think this is the case, or at least not yet.  Refer to this article from Cisco:
http://www.cisco.com/en/US/tech/tk648/tk361/technologies_q_and_a_item09186a00800e523b.shtml

"NVI stands for NAT Virtual Interface. It allows NAT to translate between two different VRFs."

There's a lot of features that aren't available on NVI-based NAT yet (such as SNAT, and some route-map configurations), and based on the above statement, I am wondering if they're planned for the future?

Anyway, how does this help our NAT/route order-of-operation problem?

NVI-based NAT "double routes".  It picks an egress interface, NATs, and then re-picks an egress interface.  This behavior is symmetric for both "inside" and "outside".  In fact, as we will see, NVI NAT eliminates the concept of inside and outside completely.

I've removed all the existing NAT configuration from R4.

R4(config)#int fa0/0
R4(config-if)#ip nat enable
R4(config)#int fa0/1
R4(config-if)#ip nat enable
R4(config)#ip nat source static 192.168.0.1 30.0.0.50
R4(config)#ip nat source static 30.0.0.5 192.168.0.50

That's it - no inside, no outside, just simple translations.

R1#ping 192.168.0.50
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 192.168.0.50, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 40/56/76 ms

R4#show ip nat nvi translations
Pro Source global      Source local       Destin  local      Destin  global
--- 192.168.0.50       30.0.0.5           ---                ---
icmp 30.0.0.50:16      192.168.0.1:16     192.168.0.50:16    30.0.0.5:16
--- 30.0.0.50          192.168.0.1        ---                ---

Note the new "show" command for NVI.
You'll also note no reference of inside or outside on the show output, everything is considered source or destination.  This makes the debugging, nat statements, etc much easier to figure out. 

A couple catches on NVI NAT.  As I mentioned above, SNAT (discussed later) is unsupported, as are route-maps for 1:1 static NATs (also discussed later).

Before we push on to policy NATs, let's take a quick look at a way to use IOS NAT as a poor man's load balancer.

I've enabled telnet on R1, R2 and R3; let's distribute inbound telnet connections from R5 amongst the three in a round-robin fashion.  I've also given all three routers a default route aimed at R4.  I've removed all the existing NAT config, again.

R4(config)#int fa0/0
R4(config-if)#ip nat outside
R4(config-if)#int fa0/1
R4(config-if)#ip nat inside
R4(config-if)#exit
R4(config)#ip access-list sta vip
R4(config-std-nacl)#permit 30.0.0.25
R4(config-std-nacl)#exit
R4(config)#ip nat pool server-pool 192.168.0.1 192.168.0.3 netmask 255.255.255.0 type rotary
R4(config)#ip nat inside destination list vip pool server-pool

In this case, 30.0.0.25 is our virtual IP (VIP) on the outside. 

and...

R5#telnet 30.0.0.25
Trying 30.0.0.25 ...
% Connection timed out; remote host not responding

That was anti-climatic. 

I'm not sure what causes the problem, but sometimes when I set this up, the router refuses to automatically ARP for the VIP:

R4(config)#do sh ip alias
Address Type             IP Address      Port
Interface                4.4.4.4
Interface                30.0.0.4
Interface                192.168.0.4

But we can force the behavior:

R4(config)#ip alias 30.0.0.25 23
R4(config)#do sh ip alias
Address Type             IP Address      Port
Interface                4.4.4.4
Interface                30.0.0.4
Alias                    30.0.0.25      23
Interface                192.168.0.4

and now it should work:

R5#telnet 30.0.0.25
Trying 30.0.0.25 ... Open

User Access Verification
Password:
R1>exit

[Connection to 30.0.0.25 closed by foreign host]
R5#telnet 30.0.0.25
Trying 30.0.0.25 ... Open

User Access Verification
Password:
R2>exit
[Connection to 30.0.0.25 closed by foreign host]
R5#telnet 30.0.0.25
Trying 30.0.0.25 ... Open

User Access Verification
Password:
R3>

Now that we have that working, what if R1-R3 want to access the outside?

R1#ping 30.0.0.5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 30.0.0.5, timeout is 2 seconds:
.....
Success rate is 0 percent (0/5)

No luck, we haven't setup an inside->outside translation.
Let's build a dynamic PAT.

R4(config)#ip access-list sta inside
R4(config-std-nacl)#permit 192.168.0.0 0.0.0.255
R4(config)#ip nat inside source list inside interface fa0/0

R1#ping 30.0.0.5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 30.0.0.5, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 36/52/112 ms

Now we have outside->inside load balancing, and inside->outside dynamic PAT.

If you've ever used a "lesser" router and tried to forward a range of ports (say TCP 10 through 30) from the outside to an inside address, you probably did it with relative ease.  You may have also struggled trying to get this to work in the Cisco world, which does "port forwards" one at a time via static PAT.  There's a workaround to be had using this same NAT Rotary feature:

First we remove the old config:

R4(config)#no ip nat pool server-pool 192.168.0.1 192.168.0.3 netmask 255.255.255.0 type rotary
R4(config)#no ip nat inside destination list vip pool server-pool

Then we implement the workaround.  We want to forward TCP 10 - 30 to R1.

Create a rotary pool of just R1:

R4(config)#no ip nat pool server-pool 192.168.0.1 192.168.0.1 netmask 255.255.255.0 type rotary

Create an access-list specifying the traffic to "rotary load-balance" to our single server:

R4(config)#ip access-list extended port-forwarding
R4(config-ext-nacl)#permit tcp any any range 10 30
R4(config-ext-nacl)#exit

And apply the policy:

R4(config)#ip nat inside destination list port-forwarding pool server-pool

And test:

R5#telnet 30.0.0.4
Trying 30.0.0.4 ... Open

User Access Verification
Password:
R1>exit

[Connection to 30.0.0.4 closed by foreign host]

Telnet (TCP 23) works.

R5#telnet 30.0.0.4 19
Trying 30.0.0.4, 19 ... Open
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh
chargen (TCP 19) works as well.

Let's move on to policy NAT now.

Policy NAT with Extended Access Lists

The simplest way to create a policy NAT is to just use an extended access list.  Up until now, we've been using standard access-lists, which create a simple logic: If the source is on this list, change it.  Now we can say things like "If source is on this list and you're headed towards a specific IP range, then change it".  In my experience, this is most useful for VPNs, where you want to PAT towards the Internet but dynamic NAT to another range over the VPN tunnel.  I'm not going to build that elaborate of a lab, but now you have a reference point for production use.

Some additions to our diagram:


I've given R4 two options for routing "outside": R5 and R6.
R5 has the same IPs as before - the interface IPs between R4 and R5 are 30.0.0.0/24
R6 is using 31.0.0.0/24 between R4 and R6. 

Pretend R7 doesn't exist for now-- we'll get there.

I've removed all the existing NAT config on R4.

R4(config)#int fa0/1
R4(config-if)#ip nat inside
R4(config-if)#int fa0/0
R4(config-if)#ip nat outside
R4(config-if)#int fa1/0
R4(config-if)#ip nat outside

Now that you can reach either R5 or R6 from R4, we need to NAT differently depending on which direction we're going.

One access-list for traffic headed towards R5:
R4(config)#ip access-list ext towards-r5
R4(config-ext-nacl)#permit ip 192.168.0.0 0.0.0.255 30.0.0.0 0.0.0.255

Another access-list for traffic headed towards R6:
R4(config)#ip access-list ext towards-r6
R4(config-ext-nacl)#permit ip 192.168.0.0 0.0.0.255 30.1.0.0 0.0.0.255

Match them in the NAT statements with the appropriate interface:
R4(config)#ip nat inside source list towards-r5 interface fa0/0 overload
R4(config)#ip nat inside source list towards-r6 interface fa1/0 overload

R1#ping 30.0.0.5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 30.0.0.5, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 16/47/92 ms

R1#ping 30.1.0.6
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 30.1.0.6, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 20/52/116 ms

Pings to R5 and R6 succeed -

R4#sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
icmp 30.1.0.4:22       192.168.0.1:22     30.1.0.6:22        30.1.0.6:22
icmp 30.0.0.4:23       192.168.0.1:23     30.0.0.5:23        30.0.0.5:23

and are appropriately NATed.

Now if you're paying attention, you may have already noticed the limitation of this method when used with my diagram.  "Pretend R7 doesn't exist", I said.  What if we're trying to reach 7.7.7.7 using the extended-access list policy NAT method?  Both our access-lists would read the same:

permit ip 192.168.0.0 0.0.0.255 7.7.7.7 0.0.0.0

That's not going to work. In fact, what if the destination was the Internet?  Your access-lists might look like:

permit ip 192.168.0.0 0.0.0.255 any

That's not going to work either. 

Here's where route-maps come in to policy PATs.

Policy NAT with Route-Maps

Cleanup from earlier...
R4(config)#no ip nat inside source list towards-r5 interface fa0/1 overload
R4(config)#no ip nat inside source list towards-r6 interface fa1/0 overload

Build
R4(config)#ip access-list extended towards-outside
R4(config-ext-nacl)#permit ip 192.168.0.0 0.0.0.255 any

Route-maps can do two functions:
1) match access-lists
2) match egress interfaces or next-hops

Some examples will also show them setting interfaces (or next-hops), but I've not seen a functional difference between using "set interface" and "match interface" with policy NAT.  If anyone knows a difference, please comment!

R4(config)#route-map towards-r6 permit 10
R4(config-route-map)#match ip address towards-outside
R4(config-route-map)#match interface FastEthernet1/0
R4(config)#route-map towards-r5 permit 10
R4(config-route-map)#match ip address towards-outside
R4(config-route-map)#match interface FastEthernet0/0

R4(config)#ip nat inside source route-map towards-r5 interface FastEthernet0/0 overload
R4(config)#ip nat inside source route-map towards-r6 interface FastEthernet1/0 overload
R4(config)#ip route 0.0.0.0 0.0.0.0 30.0.0.5
R4(config)#ip route 0.0.0.0 0.0.0.0 30.1.0.6 10

This would simulate a poor man's redundant Internet solution - different static IPs on different ISPs, routing out one at a time.  If Fa0/0 goes down, Fa1/0 will take over.  Let's give it a try:

R1#ping 7.7.7.7
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 7.7.7.7, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 60/76/112 ms

R4#sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
icmp 30.0.0.4:40       192.168.0.1:40     7.7.7.7:40         7.7.7.7:40

And now for the failover test:

R4#conf t
Enter configuration commands, one per line.  End with CNTL/Z.
R4(config)#int fa0/0
R4(config-if)#shut

R1#ping 7.7.7.7
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 7.7.7.7, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 48/74/100 ms

R4#sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
icmp 30.1.0.4:41       192.168.0.1:41     7.7.7.7:41         7.7.7.7:41

Note that the "match ip address" clause in the route-map is really not necessary in this case, but I included it to show the functionality.  "match interface" is sufficient to make the NAT decision.

We saw earlier than dynamic NAT is typically reversible.  Not so much with route-maps for dynamic NAT.

Reversible Dynamic NAT with Route-Maps

R4(config)#route-map towards-r5 permit 10
R4(config-route-map)#no match interface FastEthernet0/0  ! interface not supported w/ reversible

R4(config)#no ip nat inside source route-map towards-r5 interface FastEthernet0/0 overload
R4(config)#int fa0/0
R4(config-if)#no shut

R4(config)#ip nat pool dynamic-pool 30.0.0.10 30.0.0.100 netmask 255.255.255.0
R4(config)#ip nat inside source route-map towards-r5 pool dynamic-pool reversible

The reversible keyword is required in order to make this scenario happen with route-maps.

Static NAT with Route-Maps

I've cleared all the NAT off R4 again.

R4(config)#int fa0/1
R4(config-if)#ip nat inside
R4(config-if)#int fa0/0
R4(config-if)#ip nat outside
R4(config-if)#int fa1/0
R4(config-if)#ip nat outside

I showed how to do policy-PAT already, but 1:1 is a whole different story.  Let's say this is a server farm, we have two different ISPs, but we're not running BGP and we have separate IP ranges statically assigned from both ISPs.  How do we do a hot/cold failover but maintain the static NAT?

Let's make 192.168.0.1 our "server" and try to forward traffic from two outside IPs towards it.

R4(config)#ip nat inside source static 192.168.0.1 30.0.0.1
R4(config)#ip nat inside source static 192.168.0.1 30.1.0.1
% 192.168.0.1 already mapped (192.168.0.1 -> 30.0.0.1)

You had probably already guessed that that wasn't going to work.

Here's the route-map method to accomplish this:

R4(config)#no ip nat inside source static 192.168.0.1 30.0.0.1
R4(config)#ip access-list extended towards-outside
R4(config-ext-nacl)#permit ip 192.168.0.0 0.0.0.255 any

R4(config)#ip route inside source static 192.168.0.1 30.0.0.1 route-map R1TOANY_VIAISP1
R4(config)#ip route inside source static 192.168.0.1 30.1.0.1 route-map R1TOANY_VIAISP2

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
--- 30.0.0.1           192.168.0.1        ---                ---
--- 30.1.0.1           192.168.0.1        ---                ---

We still have these in place:
ip route 0.0.0.0 0.0.0.0 30.0.0.5
ip route 0.0.0.0 0.0.0.0 30.1.0.6 10

Verification -

R5#telnet 30.0.0.1
Trying 30.0.0.1 ... Open

User Access Verification
Password:
R1>

R4(config)#int fa0/0
R4(config-if)#shut

R6#telnet 30.1.0.1
Trying 30.1.0.1 ... Open

User Access Verification
Password:
R1>

R4(config)#int fa0/0
R4(config-if)#no shut

There's another way to accomplish something similar.  The extendable command makes sort of a reverse-PAT function for 1:1 NATs.

R4(config)#no ip nat inside source static 192.168.0.1 30.0.0.1 route-map R1TOANY_VIAISP1
R4(config)#no ip nat inside source static 192.168.0.1 30.1.0.1 route-map R1TOANY_VIAISP2

R4(config)#ip nat inside source static 192.168.0.1 30.0.0.1 extendable
R4(config)#ip nat inside source static 192.168.0.1 30.1.0.1 extendable

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
--- 30.0.0.1           192.168.0.1        ---                ---
--- 30.1.0.1           192.168.0.1        ---                ---

Hmm, the NAT table looks about the same.

R5#telnet 30.0.0.1
Trying 30.0.0.1 ... Open

User Access Verification
Password:
R1>

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
tcp 30.0.0.1:23        192.168.0.1:23     30.0.0.5:16608     30.0.0.5:16608
--- 30.0.0.1           192.168.0.1        ---                ---
--- 30.1.0.1           192.168.0.1        ---                ---

We'd expect an entry like this based on the default ip nat create flow-entries.  However, this time, it's taken... more literally.  The router is doing what I can only describe as a bi-directional PAT. 

R6#telnet 30.1.0.1
Trying 30.1.0.1 ... Open

User Access Verification
Password:
R1>

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
tcp 30.0.0.1:23        192.168.0.1:23     30.0.0.5:16608     30.0.0.5:16608
tcp 30.1.0.1:23        192.168.0.1:23     30.1.0.6:16284     30.1.0.6:16284
--- 30.0.0.1           192.168.0.1        ---                ---
--- 30.1.0.1           192.168.0.1        ---                ---

Those entries are there for more than just acceleration, they're actually required now.  In fact, I got curious and disabled ip nat create flow-entries:

R4(config)#no ip nat create flow-entries
R4(config)#do clear ip nat trans *

<sample telnets were done here, not shown>

R4(config)#do sh ip nat trans
Pro Inside global      Inside local       Outside local      Outside global
tcp 30.0.0.1:23        192.168.0.1:23     30.0.0.5:59295     30.0.0.5:59295
tcp 30.1.0.1:23        192.168.0.1:23     30.1.0.6:16284     30.1.0.6:16284
tcp 30.1.0.1:23        192.168.0.1:23     30.1.0.6:63205     30.1.0.6:63205
--- 30.0.0.1           192.168.0.1        ---                ---
--- 30.1.0.1           192.168.0.1        ---                ---

Tough luck, you're getting the flow entries anyway, because this process doesn't work without them.

Arbitrary IPs; Redistributing NAT

Here's something I was always curious about - NATing to totally arbitrary IPs in IOS.
You've certainly gathered by now that you can NAT to anything, even IPs that aren't on any of your interfaces.  That's generally pretty useless because other devices aren't aware how to reach the IPs, whether your router ARPs for them or not.

I've cleared all the prior NAT configuration.  I'd like to NAT 192.168.0.0/24, our inside range, to 207.50.50.0/24.  I don't want to put 207.50.50.0/24 on an interface. I'm going to use NVI NAT for the example, but something similar could be accomplished with domain NAT. 

R4(config)#interface fa0/0
R4(config-if)#ip nat enable
R4(config)#interface fa0/1
R4(config-if)#ip nat enable

R4(config)#ip nat pool arbitrary 207.50.50.1 207.50.50.200 prefix-length 24 add-route

You may remember "add-route" from domain NAT; here it is for NVI NAT.  Note it's applied on the pool with NVI NAT instead of in the NAT statement itself.

R4(config)#ip access-list extended inside-range
R4(config-ext-nacl)#permit ip 192.168.0.0 0.0.0.255 any

R4(config)#ip nat source list inside-range pool arbitrary

NAT is all setup now.

R4(config)#do sh ip route static
S    207.50.50.0/24 [0/0] via 0.0.0.0, NVI0

This static route can now be introduced into our outside routing protocol through redistribution.  Or, you could just use a bgp "network" statement: network 207.50.50.0 mask 255.255.255.0.  In our case, the outside is presently running OSPF, so:

R4(config)#router ospf 1
R4(config-router)#redistribute static subnets

Verify -

R5#sh ip route ospf
O E2 207.50.50.0/24 [110/20] via 30.0.0.4, 00:19:24, FastEthernet0/0
R5#debug ip icmp

R1#ping 30.0.0.5
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 30.0.0.5, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 36/48/88 ms

R5#
*Mar  1 00:36:11.983: ICMP: echo reply sent, src 30.0.0.5, dst 207.50.50.1
*Mar  1 00:36:12.019: ICMP: echo reply sent, src 30.0.0.5, dst 207.50.50.1
*Mar  1 00:36:12.055: ICMP: echo reply sent, src 30.0.0.5, dst 207.50.50.1
*Mar  1 00:36:12.115: ICMP: echo reply sent, src 30.0.0.5, dst 207.50.50.1
*Mar  1 00:36:12.159: ICMP: echo reply sent, src 30.0.0.5, dst 207.50.50.1

Something similar could also be accomplished by creating a static route to null0 - ip route 207.50.50.0 255.255.255.0 null0 - and redistributing it. 

Stateful NAT (SNAT)

We're still missing one big topic in this article still: SNAT, or Stateful NAT.  It's a way of sharing NAT tables across multiple routers, typically via HSRP, for the purpose of hot/hot shared NAT or hot/cold shared NAT.  This method could literally take a blog to itself... in fact, it did!  I had to put it into production a few months ago, so I took the time to blog about it then:

http://brbccie.blogspot.com/2013/03/stateful-nat-with-asymmetric-routing.html

Hope you enjoyed,

Jeff Kronlage


4 comments:

  1. Dear Jeff,
    this is awesome.. i do not have words to express the happiness that i got after reading this blog.. you have cleared all my questions about NAT on IOS...

    ReplyDelete
  2. Hi Jeff,

    I've tried so hard to understand the deference between ip nat inside and outside, what i know is that outside perform translation before routing and inside perform routing then translation. the only answer im getting is the outside is for public which is not what i want. i need to understand the behavior of the routing and natting together .

    Thanks,

    Khaled

    ReplyDelete
  3. The best NAT explanation on the Internet. Period. Thank you Sir.

    ReplyDelete