How to Masquerade / NAT IPv4 traffic using ufw on Ubuntu CLI

This is a detailed guide on how to Masquerade / NAT IP traffic on Ubuntu CLI.

Warning: Please make sure that you have access to the device you are working on as making changes to the ufw could potentially lock you out of your machine if working remotely.

1. nano /etc/default/ufw

Enable packet forwarding by editing DEFAULT_FORWARD_POLICY=”ACCEPT”

root@test:~# nano /etc/default/ufw
# Set the default forward policy to ACCEPT, DROP or REJECT.  Please note that
# if you change this you will most likely want to adjust your rules
DEFAULT_FORWARD_POLICY="ACCEPT"

2. nano /etc/ufw/sysctl.conf

Uncomment net/ipv4/ip_forward=1 (remove the # symbol)

# Uncomment this to allow this host to route packets between interfaces
net/ipv4/ip_forward=1
#net/ipv6/conf/default/forwarding=1
#net/ipv6/conf/all/forwarding=1

3. nano /etc/ufw/before.rules 

Add the following to /etc/ufw/before.rules 

This will need to be added to the top of the file – please see example below.

Make sure you specify the source subnet you are wanting to NAT and the destination interface where your Public IP address is configured. The example below is 10.0.125.0/24 (source) and destination interface is eth1.

# nat Table rules
*nat
:POSTROUTING ACCEPT [0:0]

# Forward traffic from eth1 through eth0.
-A POSTROUTING -s 10.0.125.0/24 -o eth1 -j MASQUERADE

# don't delete the 'COMMIT' line or these nat table rules won't be processed
COMMIT

IMPORTANT: I have added the whole file as a reference below , so you can see the positioning of the lines.

Example:

root@test:~# cat /etc/ufw/before.rules
#
# rules.before
#
# Rules that should be run before the ufw command line added rules. Custom
# rules should be added to one of these chains:
#   ufw-before-input
#   ufw-before-output
#   ufw-before-forward
#

# nat Table rules
*nat
:POSTROUTING ACCEPT [0:0]

# Forward traffic from eth1 through eth0.
-A POSTROUTING -s 10.0.125.0/24 -o eth1 -j MASQUERADE

# don't delete the 'COMMIT' line or these nat table rules won't be processed
COMMIT

# Don't delete these required lines, otherwise there will be errors
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
:ufw-not-local - [0:0]
# End required lines


# allow all on loopback
-A ufw-before-input -i lo -j ACCEPT
-A ufw-before-output -o lo -j ACCEPT

# quickly process packets for which we already have a connection
-A ufw-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-output -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-forward -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# drop INVALID packets (logs these in loglevel medium and higher)
-A ufw-before-input -m conntrack --ctstate INVALID -j ufw-logging-deny
-A ufw-before-input -m conntrack --ctstate INVALID -j DROP

# ok icmp codes for INPUT
-A ufw-before-input -p icmp --icmp-type destination-unreachable -j ACCEPT
-A ufw-before-input -p icmp --icmp-type source-quench -j ACCEPT
-A ufw-before-input -p icmp --icmp-type time-exceeded -j ACCEPT
-A ufw-before-input -p icmp --icmp-type parameter-problem -j ACCEPT
-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT

# ok icmp code for FORWARD
-A ufw-before-forward -p icmp --icmp-type destination-unreachable -j ACCEPT
-A ufw-before-forward -p icmp --icmp-type source-quench -j ACCEPT
-A ufw-before-forward -p icmp --icmp-type time-exceeded -j ACCEPT
-A ufw-before-forward -p icmp --icmp-type parameter-problem -j ACCEPT
-A ufw-before-forward -p icmp --icmp-type echo-request -j ACCEPT

# allow dhcp client to work
-A ufw-before-input -p udp --sport 67 --dport 68 -j ACCEPT

#
# ufw-not-local
#
-A ufw-before-input -j ufw-not-local

# if LOCAL, RETURN
-A ufw-not-local -m addrtype --dst-type LOCAL -j RETURN

# if MULTICAST, RETURN
-A ufw-not-local -m addrtype --dst-type MULTICAST -j RETURN

# if BROADCAST, RETURN
-A ufw-not-local -m addrtype --dst-type BROADCAST -j RETURN

# all other non-local packets are dropped
-A ufw-not-local -m limit --limit 3/min --limit-burst 10 -j ufw-logging-deny
-A ufw-not-local -j DROP

# allow MULTICAST mDNS for service discovery (be sure the MULTICAST line above
# is uncommented)
-A ufw-before-input -p udp -d 224.0.0.251 --dport 5353 -j ACCEPT

# allow MULTICAST UPnP for service discovery (be sure the MULTICAST line above
# is uncommented)
-A ufw-before-input -p udp -d 239.255.255.250 --dport 1900 -j ACCEPT

# don't delete the 'COMMIT' line or these rules won't be processed
COMMIT

4. ufw disable && ufw enable

root@test:~# ufw disable && ufw enable
Firewall stopped and disabled on system startup
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
root@test:~#

5. Check that the configuration is correct

You can check the ufw policy using ” iptables -t nat -L -v”

root@test:~# iptables -t nat -L -v 
Chain POSTROUTING (policy ACCEPT 3 packets, 149 bytes)
 pkts bytes target     prot opt in     out     source               destination 
   20  1537 MASQUERADE  all  --  any    eth1    10.0.125.0/24        anywhere   

Check that you have the correct public IP address using curl https://ipinfo.io/ip

root@test:~# curl https://ipinfo.io/ip
91.203.x.x

6. Test the connection

root@GNS3-Server:~# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=55 time=6.15 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=55 time=6.20 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=55 time=6.21 ms

7. Troubleshoot using tcpdump

Make sure traffic is traversing the Ubuntu device where you have configured the NAT.

root@test:~# tcpdump -i eth2 -c 200
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth2, link-type EN10MB (Ethernet), capture size 262144 bytes
10:39:47.260322 IP 10.0.125.10 > google-public-dns-a.google.com: ICMP echo request, id 8787, seq 5, length 64
10:39:47.265931 IP google-public-dns-a.google.com > 10.0.125.10: ICMP echo reply, id 8787, seq 5, length 64
10:39:48.261547 IP 10.0.125.10 > google-public-dns-a.google.com: ICMP echo request, id 8787, seq 6, length 64
10:39:48.267106 IP google-public-dns-a.google.com > 10.0.125.10: ICMP echo reply, id 8787, seq 6, length 64
10:39:49.262849 IP 10.0.125.10 > google-public-dns-a.google.com: ICMP echo request, id 8787, seq 7, length 64
10:39:49.268466 IP google-public-dns-a.google.com > 10.0.125.10: ICMP echo reply, id 8787, seq 7, length 64

If you are new to the world of Linux, an avid Linux enthusiast or a student why not try our 0.99p per month Linux VPS.

Simply click on the screen shot below to find out more or navigate to https://piggybank.cloud

Thank you for reading and please feel free to leave any feedback.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s