Create a router between 2 VMs on Proxmox /
Connect 2 machines in different subnets via a homemade router

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

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:

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