iptables – NAT MASQUERADING in Linux
In a previous post I wrote about how can we accomplish NAT with Cisco devices. This time let’s examine how can we implement the same thing in Linux with the topology below. Basically we’re going to turn our Kali1 machine into a router which is a Docker instance running in GNS3.
Step 1: GET AN ADDRESS VIA DHCP ON ETH0, TEST CONNECTION
First I leased an IP address via my AP with the dhclient command (192.168.122.226). Yes, it’s a private RFC 1918 address, and our network is double NATted, but for the purpose of this demonstration let’s imagine that it is a public address, and eth1 faces our local network with private addresses from the 10.0.0.0/24 subnet.
root@KaliLinuxCLI-1:/# dhclient -v
Internet Systems Consortium DHCP Client 4.3.5
Copyright 2004-2016 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/
Listening on LPF/eth1/ae:a7:45:e6:6a:bd
Sending on LPF/eth1/ae:a7:45:e6:6a:bd
Listening on LPF/eth0/be:87:e3:a8:24:88
Sending on LPF/eth0/be:87:e3:a8:24:88
Sending on Socket/fallback
DHCPDISCOVER on eth1 to 255.255.255.255 port 67 interval 3
DHCPREQUEST of 192.168.122.226 on eth0 to 255.255.255.255 port 67
DHCPNAK from 192.168.122.1
DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval 4
DHCPDISCOVER on eth1 to 255.255.255.255 port 67 interval 3
DHCPREQUEST of 192.168.122.206 on eth0 to 255.255.255.255 port 67
DHCPOFFER of 192.168.122.206 from 192.168.122.1
DHCPACK of 192.168.122.206 from 192.168.122.1
bound to 192.168.122.206 -- renewal in 1795 seconds.
Let’s verify our interface configurations with the ip command.
root@KaliLinuxCLI-1:/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever
11: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000 link/ether be:87:e3:a8:24:88 brd ff:ff:ff:ff:ff:ff inet 192.168.122.206/24 brd 192.168.122.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::bc87:e3ff:fea8:2488/64 scope link valid_lft forever preferred_lft forever
12: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000 link/ether ae:a7:45:e6:6a:bd brd ff:ff:ff:ff:ff:ff inet6 fe80::aca7:45ff:fee6:6abd/64 scope link valid_lft forever preferred_lft forever
eth0 got its address, I tested my connection as well as the DNS name resolution by pinging an address on the internet.
root@KaliLinuxCLI-1:/# ping -c1 google.com
PING google.com (142.251.39.78) 56(84) bytes of data.
64 bytes from bud02s39-in-f14.1e100.net (142.251.39.78): icmp_seq=1 ttl=113 time=45.4 ms
--- google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 45.496/45.496/45.496/0.000 ms
Step 2: ASSIGN IP ADDRESS MANUALLY TO ETH1
We’re going to configure the eth1 interface of the Kali1 machine manually (although we’ve a DHCP server on the local network – we’ll be dealing with that later). This interface will be the default gateway for the hosts on the local network, so it’s reasonable to have a static address.
root@KaliLinuxCLI-1:/# ip address add 10.0.0.254/24 dev eth1
root@KaliLinuxCLI-1:/# ip link set eth1 up
root@KaliLinuxCLI-1:/# ip addr
1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
11: eth0: mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
link/ether be:87:e3:a8:24:88 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.206/24 brd 192.168.122.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::bc87:e3ff:fea8:2488/64 scope link
valid_lft forever preferred_lft forever
12: eth1: mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
link/ether ae:a7:45:e6:6a:bd brd ff:ff:ff:ff:ff:ff
inet 10.0.0.254/24 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::aca7:45ff:fee6:6abd/64 scope link
valid_lft forever preferred_lft forever
Or we could’ve done the same thing using the deprecated ifconfig command.
root@KaliLinuxCLI-1:/# ifconfig eth1 10.0.0.254 10.0.0.254 netmask 255.255.255.0 up
root@KaliLinuxCLI-1:/# ifconfig
eth0: flags=4163 mtu 1500
inet 192.168.122.206 netmask 255.255.255.0 broadcast 192.168.122.255
inet6 fe80::bc87:e3ff:fea8:2488 prefixlen 64 scopeid 0x20
ether be:87:e3:a8:24:88 txqueuelen 1000 (Ethernet)
RX packets 1242 bytes 110028 (107.4 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 72 bytes 6306 (6.1 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
eth1: flags=4163 mtu 1500
inet 10.0.0.254 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::aca7:45ff:fee6:6abd prefixlen 64 scopeid 0x20
ether ae:a7:45:e6:6a:bd txqueuelen 1000 (Ethernet)
RX packets 1239 bytes 95355 (93.1 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 55 bytes 13738 (13.4 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73 mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Make sure that our routing table is correct:
root@KaliLinuxCLI-1:/# ip route
default via 192.168.122.1 dev eth0
10.0.0.0/24 dev eth1 proto kernel scope link src 10.0.0.254
192.168.122.0/24 dev eth0 proto kernel scope link src 192.168.122.206
We have a default route through eth0 which we’ve just got via DHCP in Step 1.
Step 3: ENABLE PACKET FORWARDING
To turn our Kali1 machine into a router we need to modify an important kernel parameter in Linux with the sysctl utility. To be able to forward packets between our interfaces we need to make sure that the net.ipv4.ip_forwarding kernel parameter is enabled. We can verify this with one of the following commands:
root@KaliLinuxCLI-1:/# sysctl -a | grep ip_forward
net.ipv4.ip_forward = 1
root@KaliLinuxCLI-1:/# cat /proc/sys/net/ipv4/ip_forward
1
1 in this case means that the IP forwarding is enabled. If it shows 0 aka. disabled we can turn IP forwarding on by issuing one of the following commands:
echo "1" > /proc/sys/net/ipv4/ip_forward
or
sysctl -w net.ipv4.ip_forwarding=1
Either of them is a complete solution. But a useful reminder that by issuing these commands the kernel parameter we’ve just enabled won’t be persistent, that is we’re going to lose our settings after system reboot.
To make our settings we’ve configured above persistent we also have 2 options. Option no.1 is the easiest, just issue the following command:
sysctl -p
Option no.2 edit the /etc/sysctl.conf file by uncommenting the following line:
net.ipv4.ip_forward=1
Step 4: CONFIGURE R1 AS DHCP SERVER FOR LOCAL NETWORK
For the hosts in the local network I designated a Cisco IOSv router as a DHCP server for assigning IP addresses for the three hosts. I could’ve assigned addresses for them statically via the ip or ifconfig utility, but this method doesn’t scale, so decided to designate a DHCP server for this purpose. Here is the configuration of R1 (some Cisco stuff):
R1(config)#int g0/0
R1(config-if)#ip address 10.0.0.253 255.255.255.0
R1(config-if)#no shut
R1(config)#ip dhcp excluded-address 10.0.0.200 10.0.0.254
R1(config)#ip dhcp pool TEST_POOL
R1(dhcp-config)#network 10.0.0.0 255.255.255.0
R1(dhcp-config)#default-router 10.0.0.254
R1(dhcp-config)#dns-server 8.8.8.8
I checked that the addresses have been assigned correctly to the three hosts:
R1#show ip dhcp binding
Bindings from all pools not associated with VRF:
IP address Client-ID/ Lease expiration Type
Hardware address/
User name
10.0.0.1 d6e2.0b06.111e Dec 29 2023 09:32 AM Automatic
10.0.0.2 92d0.3e76.d850 Dec 29 2023 09:32 AM Automatic
10.0.0.3 0ed2.762d.5097 Dec 29 2023 09:32 AM Automatic
Step 5: CONFIGURE NAT MASQUERADE WITH IPTABLES
The most important thing: we need to edit our firewall rules with iptables on the Kali1 machine. To perform NAT we need to configure the NAT table (-t NAT) instead of the filter table which is the default. We define our rule on the POSTROUTING chain which means we’re going to “translate” the internal addresses after routing calculations. With the -o flag we appoint the output interface interface, eth0 in this case.
root@KaliLinuxCLI-1:/# iptables -t nat -A POSTROUTING -o eth0 -s 10.0.0.0/24 -j MASQUERADE
root@KaliLinuxCLI-1:/# iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 10.0.0.0/24 anywhere
So by implementing NAT we “translate” the source IP addresses of our packages: Kali1 simply switches the source IP address of our internal hosts to the address of his eth0 interface.
To visualize what we’ve just done let’s view some packet capture between our Kali1 and the NAT Cloud and between Kali1 and the IOSvL2 switch.
As it can be seen the source IP addresses have been swapped.
By doing that we hide the addresses of our internal hosts from the outside network. We also only need one public address for many hosts.