05/11/2025
This project is purely an experience to observe how a router works by creating our own. We will create two VMs in different subnets, and we are going to create a router ourselves based on iptables to connect the two machine and also give them access to the internet with NAT
Setup of the experiment
| Machine | Interface | Bridge | IP |
|---|---|---|---|
| PC1 | eth0 | vmbr10 | 192.168.10.2/24 |
| ROUTER | eth0 | vmbr10 | 192.168.10.1/24 |
| ROUTER | eth1 | vmbr20 | 192.168.20.1/24 |
| PC2 | eth0 | vmbr20 | 192.168.20.2/24 |
Bridge ( vmbr 10 and 20)

You can create the bridge by modifying this file (on Proxmox).
micro /etc/network/interfaces
By adding
# start
auto vmbr10
iface vmbr10 inet manual
bridge-ports none
bridge-stp off
bridge-fd 0
auto vmbr20
iface vmbr20 inet manual
bridge-ports none
bridge-stp off
bridge-fd 0
# end
Then :
systemctl restart networking
pc1

pc2

router

Explications
- The ROUTER is theonly container/machine that has 2 network interfaces :
- eth0 → connect to LAN1
-
eth1 → connect to LAN2
-
Each LAN is isolated, but the router is connected to both, so it « overlaps » them.
Test
pc1
root@experimentalPc1:~# ip a s | grep 192
inet 192.168.10.2/24 brd 192.168.10.255 scope global eth0
pc1 has the ip 192.168.10.2 and it’s in the subnet 192.168.10.255
So it cannot reach pc2 at 192.168.20.2 because they are not in the same subnet
But we have a router that is connected to both networks:
The router
The router is well connected in both networks, it’s in both subnets.
root@experimentalRouter:~# ip a s | grep 192
inet 192.168.10.1/24 brd 192.168.10.255 scope global eth0
inet 192.168.20.1/24 brd 192.168.20.255 scope global eth1
It routes the communications after activating:
sysctl -w net.ipv4.ip_forward=1
pc 2
root@experimentalPc2:~# ip a s | grep 192
inet 192.168.20.2/24 brd 192.168.20.255 scope global eth0
pc1 has 192.168.20.2 and it’s in the subnet 192.168.20.255
And the default route, so the Gateway that will be used to try to exit the subnet if needed:
root@experimentalPc2:~# ip route
default via 192.168.20.1 dev eth0 onlink
192.168.20.0/24 dev eth0 proto kernel scope link src 192.168.20.2
Here we have our router
On the router, we will add a new interface.

Then we restart the networking part.
systemctl restart networking
This one will retrieve an IP address on the subnet 192.168.1.255 from the Router of my ISP.
That is indeed the case:
root@experimentalRouter:~# ip a s | grep 192
inet 192.168.10.1/24 brd 192.168.10.255 scope global eth0
inet 192.168.20.1/24 brd 192.168.20.255 scope global eth1
inet 192.168.1.132/24 brd 192.168.1.255 scope global dynamic eth2
Here we can see that we have retrieved a new interface and IP:
inet 192.168.1.132/24 brd 192.168.1.255 scope global dynamic eth2
Now I can go online, we can check that by pinging.
root@experimentalRouter:~# ping 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=64 time=2.59 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=64 time=0.323 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=64 time=0.363 ms
Route the addresses outside of the subnets to the internet.
When I try to go online on pc1 it doesn’t work.
root@experimentalPc1:~# ping 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
...
On the router, we will direct the traffic from machines that want to go to the Internet towards the router’s interface eth2 with:
First, we will install iptables:
apt install iptables
Then we add a NAT rule :
iptables -t nat -A POSTROUTING -o eth2 -j MASQUERADE
To make this permanent:
- Install iptables-persistent and save the rules:
apt install iptables-persistent -y
netfilter-persistent save
Now it should work !
pc1 has access to the Web via a NAT
root@experimentalPc1:~# ping 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=63 time=0.401 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=63 time=0.288 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=63 time=1.11 ms
You can check that the default gateway is indeed 192.168.10.1 for pc1 with
ip route | grep default
Which should return :
default via 192.168.10.1 dev eth0 onlink
Traceroute
You can also verify this with a traceroute:
root@experimentalPc1:~# traceroute 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
1 192.168.10.1 (192.168.10.1) 0.397 ms 0.348 ms 0.327 ms
2 192.168.1.1 (192.168.1.1) 10.426 ms 10.402 ms 10.380 ms
3 80.10.255.181 (80.10.255.181) 1.997 ms 2.074 ms 2.145 ms
Here we can see that we have gone from our router (1) 192.168.10.1
to the gateway (2) 192.168.1.1 of the router
and then outside the network (3) 80.10.255.181.
Diagram with web access:

Create firewall rules on the router with iptables.
If I want to prevent PC1 and the 192.168.10.0/24 network from communicating with PC2 on the 192.168.20.0/24 network, but keep them connected to my ISP router gateway.
On our homemade router.
Block LAN1 → LAN2
# Block LAN1 → LAN2
iptables -A FORWARD -s 192.168.10.0/24 -d 192.168.20.0/24 -j DROP
# Block LAN2 → LAN1
iptables -A FORWARD -s 192.168.20.0/24 -d 192.168.10.0/24 -j DROP
These rules only block communication between the two subnet – East – West
The traffic from LAN1 → WAN (eth2) and LAN2 → WAN remains allowed thanks to NAT.
Let’s try
Indeed it no longer works… Here I am trying to contact PC2 from PC1.
root@experimentalPc1:~# ping 192.168.20.2
PING 192.168.20.2 (192.168.20.2) 56(84) bytes of data.
...
But the Internet works perfectly.
root@experimentalPc1:~# ping 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=63 time=0.493 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=63 time=0.321 ms
But internet access is still working:
root@experimentalPc1:~# ping google.fr
PING google.fr (142.250.201.163) 56(84) bytes of data.
64 bytes from par21s23-in-f3.1e100.net (142.250.201.163): icmp_seq=1 ttl=116 time=6.91 ms
64 bytes from par21s23-in-f3.1e100.net (142.250.201.163): icmp_seq=2 ttl=116 time=7.40 ms
Allow LAN1 → LAN2
To delete the rule:
iptables -D FORWARD -s 192.168.10.0/24 -d 192.168.20.0/24 -j DROP
iptables -D FORWARD -s 192.168.20.0/24 -d 192.168.10.0/24 -j DROP
Or, if we want to change the rule and therefore keep a rule, but this time that explicitly accepts.
iptables -A FORWARD -s 192.168.10.0/24 -d 192.168.20.0/24 -j ACCEPT
iptables -A FORWARD -s 192.168.20.0/24 -d 192.168.10.0/24 -j ACCEPT
See the active iptables rules
iptables -L -v -n
See the rules of NAT.
iptables -t nat -L -v -n