VLANs are excellent for separating devices into independent Layer 2 broadcast domains.
But isolation creates another requirement:
What happens when devices in different VLANs need to communicate?
Consider a Linux environment containing:
VLAN 10 → Application Servers
VLAN 20 → Database Servers
VLAN 30 → Management Devices inside VLAN 10 can communicate with other devices in VLAN 10 through Layer 2 switching.
However:
VLAN 10
X
VLAN 20 cannot communicate directly through ordinary bridge forwarding.
Communication between those networks requires Layer 3 routing.
Traditional enterprise networks commonly solve this with a router or Layer 3 switch. Linux can perform the same routing function using network namespaces, virtual Ethernet interfaces, Linux bridges, VLAN interfaces, IP forwarding, routing tables, and firewall policies.
In this practical guide, we will build a complete Linux inter-VLAN routing lab and transform a Linux machine into a virtual router connecting two isolated VLANs.
If you have not built a VLAN-aware Linux bridge before, start with CorgiNetwork’s Linux Bridge VLAN Filtering Explained because this guide builds directly on those concepts.
For the underlying bridge architecture, read Linux Bridge Networking with Network Namespaces and veth.
A VLAN creates a separate Layer 2 broadcast domain.
Suppose we have:
VLAN 10
192.168.10.0/24 and:
VLAN 20
192.168.20.0/24 A host in VLAN 10 might use:
192.168.10.10 while a host in VLAN 20 uses:
192.168.20.20 Because these addresses belong to different IP networks, traffic between them must pass through a Layer 3 routing device.
Conceptually:
Host A
192.168.10.10
|
VLAN 10
|
v
Router
|
v
VLAN 20
|
Host B
192.168.20.20 That process is called inter-VLAN routing.
Linux includes an IP routing implementation and can therefore perform the Layer 3 function required by this design.
A Linux bridge is fundamentally a Layer 2 component.
The official Linux kernel Ethernet Bridging documentation describes a bridge as connecting network segments and forwarding Ethernet frames according to Layer 2 information.
When VLAN filtering is enabled, Linux additionally uses the VLAN information when determining appropriate bridge forwarding.
Our previous Linux Bridge VLAN Filtering guide created this architecture:
Linux Bridge
vlan_filtering=1
/ \
/ \
VLAN 10 VLAN 20
| |
Host A Host B The entire purpose of the VLAN boundary is to prevent those two Layer 2 domains from behaving as one LAN.
Therefore, the correct solution is not to defeat VLAN isolation.
We add a router.
VLAN 10
|
|
Router
|
|
VLAN 20 This preserves Layer 2 isolation while permitting controlled Layer 3 communication.
This distinction is central to the entire lab.
Communication between two VLAN 10 hosts follows approximately:
Host A
↓
ARP
↓
Ethernet
↓
Linux Bridge
↓
Host B No router is required when the destination is directly reachable within the same subnet.
The path changes:
Host A
↓
Default Gateway
↓
Router
↓
Routing Table
↓
VLAN 20
↓
Host B This is Layer 3 routing.
A useful troubleshooting rule is:
Same subnet/VLAN
↓
Investigate Layer 2 first
Different subnet
↓
Investigate gateway and routing We will build:
Linux Host
+----------------+
| br0 |
| VLAN Filtering |
+----------------+
/ \
/ \
VLAN 10 VLAN 20
| |
ns10 ns20
192.168.10.10 192.168.20.20
\ /
\ /
\ /
Router Namespace
ns-rtr
/ \
192.168.10.1 192.168.20.1 The logical Layer 3 topology is:
192.168.10.0/24
Host10
192.168.10.10
|
|
192.168.10.1
Router
192.168.20.1
|
|
Host20
192.168.20.20
192.168.20.0/24 This is effectively a small virtual enterprise network inside one Linux host.
A Linux network namespace provides its own networking context.
The ip-netns(8) documentation describes a namespace as logically another copy of the network stack with its own routes, firewall rules, and network devices.
That makes namespaces ideal for this project.
Instead of needing:
3 physical computers
+
1 switch
+
1 router we can simulate the environment inside one Linux machine.
CorgiNetwork’s Linux Network Namespaces Explained covers this architecture in depth.
Create VLAN 10’s host:
sudo ip netns add ns10 Create VLAN 20’s host:
sudo ip netns add ns20 Create the router:
sudo ip netns add ns-rtr Verify:
ip netns list You should see:
ns10
ns20
ns-rtr Bring up loopback:
sudo ip netns exec ns10 ip link set lo up
sudo ip netns exec ns20 ip link set lo up
sudo ip netns exec ns-rtr ip link set lo up The ip netns exec command executes networking commands inside the selected namespace.
Create:
sudo ip link add br0 type bridge Enable VLAN filtering:
sudo ip link set dev br0 type bridge vlan_filtering 1 Bring it up:
sudo ip link set br0 up Verify:
ip -d link show br0 Look for:
vlan_filtering 1 The Linux kernel bridge documentation notes that VLAN filtering is disabled by default and that, once enabled, forwarding takes VLAN information into account.
Create:
sudo ip link add veth10-br type veth peer name veth10 Move one endpoint into ns10:
sudo ip link set veth10 netns ns10 Attach the other endpoint to the bridge:
sudo ip link set veth10-br master br0 Bring the bridge-facing side up:
sudo ip link set veth10-br up Inside the namespace:
sudo ip netns exec ns10 ip link set veth10 up The topology is now:
ns10
|
veth10
|
veth10-br
|
br0 Configure:
sudo bridge vlan add dev veth10-br vid 10 pvid untagged Inspect:
bridge vlan show Conceptually:
ns10
|
untagged Ethernet
|
veth10-br
|
PVID 10
|
VLAN 10 Assign the host IP:
sudo ip netns exec ns10 \
ip addr add 192.168.10.10/24 dev veth10 Verify:
sudo ip netns exec ns10 ip -br addr Create:
sudo ip link add veth20-br type veth peer name veth20 Move:
sudo ip link set veth20 netns ns20 Attach:
sudo ip link set veth20-br master br0 Bring up:
sudo ip link set veth20-br up
sudo ip netns exec ns20 ip link set veth20 up Configure VLAN 20:
sudo bridge vlan add dev veth20-br vid 20 pvid untagged Assign:
sudo ip netns exec ns20 \
ip addr add 192.168.20.20/24 dev veth20 We now have:
ns10 ns20
192.168.10.10 192.168.20.20
| |
VLAN 10 VLAN 20
\ /
\ /
br0 They should remain separated at Layer 2.
From ns10:
sudo ip netns exec ns10 \
ping -c 3 192.168.20.20 This should not suddenly work merely because both endpoints share a bridge.
Why?
They belong to:
192.168.10.0/24 and:
192.168.20.0/24 and they are also separated into different VLANs.
At this stage ns10 does not even have a router capable of reaching VLAN 20.
Check:
sudo ip netns exec ns10 ip route You should primarily see the directly connected VLAN 10 network.
This is our baseline.
Always establish the broken/isolated state before adding routing.
Now create another veth pair:
sudo ip link add rtr-trunk type veth peer name rtr0 Move the router endpoint:
sudo ip link set rtr0 netns ns-rtr Attach the host endpoint:
sudo ip link set rtr-trunk master br0 Bring both sides up:
sudo ip link set rtr-trunk up
sudo ip netns exec ns-rtr ip link set rtr0 up The topology becomes:
br0
/ | \
/ | \
VLAN10 VLAN20 rtr-trunk
| | |
ns10 ns20 ns-rtr Unlike our host access ports, the router connection must carry multiple VLANs.
It therefore behaves as a trunk.
Add VLAN 10:
sudo bridge vlan add dev rtr-trunk vid 10 Add VLAN 20:
sudo bridge vlan add dev rtr-trunk vid 20 Verify:
bridge vlan show Conceptually:
veth10-br
|
VLAN 10 access
veth20-br
|
VLAN 20 access
rtr-trunk
|
+--+--+
| |
10 20
tagged This is the virtual equivalent of connecting a router to a managed switch through an 802.1Q trunk.
Inside ns-rtr, create VLAN 10:
sudo ip netns exec ns-rtr \
ip link add link rtr0 name rtr0.10 type vlan id 10 Create VLAN 20:
sudo ip netns exec ns-rtr \
ip link add link rtr0 name rtr0.20 type vlan id 20 Bring them up:
sudo ip netns exec ns-rtr ip link set rtr0.10 up
sudo ip netns exec ns-rtr ip link set rtr0.20 up Now the router has:
rtr0
|
+---- rtr0.10 → VLAN 10
|
+---- rtr0.20 → VLAN 20 This design is often called a router-on-a-stick architecture because multiple VLANs reach one routing device through a tagged trunk.
Configure VLAN 10:
sudo ip netns exec ns-rtr \
ip addr add 192.168.10.1/24 dev rtr0.10 Configure VLAN 20:
sudo ip netns exec ns-rtr \
ip addr add 192.168.20.1/24 dev rtr0.20 Inspect:
sudo ip netns exec ns-rtr ip -br addr Expected conceptually:
rtr0.10 192.168.10.1/24
rtr0.20 192.168.20.1/24 The router is now directly connected to both networks.
Run:
sudo ip netns exec ns-rtr ip route You should see routes equivalent to:
192.168.10.0/24 dev rtr0.10
192.168.20.0/24 dev rtr0.20 No static route between these networks is required because both are directly connected to the router.
The Linux ip route interface manipulates kernel routing-table entries and supports directly connected and gateway-based routes.
The routing decision is now straightforward:
Destination 192.168.10.x
↓
rtr0.10
Destination 192.168.20.x
↓
rtr0.20 From VLAN 10:
sudo ip netns exec ns10 \
ping -c 3 192.168.10.1 From VLAN 20:
sudo ip netns exec ns20 \
ping -c 3 192.168.20.1 Do not continue until both tests work.
If:
ns10 → 192.168.10.1 fails, inter-VLAN routing is not yet the problem.
You have a Layer 2/VLAN issue.
Likewise, failure of:
ns20 → 192.168.20.1 should send you back to VLAN 20 configuration.
Tell ns10 that destinations outside its subnet should use:
192.168.10.1 Run:
sudo ip netns exec ns10 \
ip route add default via 192.168.10.1 For ns20:
sudo ip netns exec ns20 \
ip route add default via 192.168.20.1 Verify VLAN 10:
sudo ip netns exec ns10 ip route Expected:
192.168.10.0/24 dev veth10
default via 192.168.10.1 VLAN 20:
sudo ip netns exec ns20 ip route Expected:
192.168.20.0/24 dev veth20
default via 192.168.20.1 Now each endpoint knows where to send traffic destined for another network.
From ns10:
sudo ip netns exec ns10 \
ip route get 192.168.20.20 The result should point toward:
192.168.10.1 Now ask the router:
sudo ip netns exec ns-rtr \
ip route get 192.168.20.20 It should select:
rtr0.20 The ip route get command is particularly valuable because it asks the kernel to resolve a specific route lookup instead of merely listing routes.
This is also one of the core techniques in CorgiNetwork’s Linux Policy Routing with ip rule and Multiple Routing Tables.
The router owns addresses on both networks, but it must also be allowed to forward packets between them.
Check inside the router namespace:
sudo ip netns exec ns-rtr \
sysctl net.ipv4.ip_forward Enable it for this lab:
sudo ip netns exec ns-rtr \
sysctl -w net.ipv4.ip_forward=1 Verify:
sudo ip netns exec ns-rtr \
sysctl net.ipv4.ip_forward Expected:
net.ipv4.ip_forward = 1 The distinction is important:
Router has both networks
≠
Router necessarily forwards packets IP forwarding turns the namespace from a multi-homed endpoint into an IPv4 forwarding device for this lab.
Now run:
sudo ip netns exec ns10 \
ping -c 4 192.168.20.20 The packet path is:
ns10
192.168.10.10
|
v
192.168.10.1
ns-rtr
|
routing lookup
|
v
192.168.20.1
|
v
ns20
192.168.20.20 Then test the reverse direction:
sudo ip netns exec ns20 \
ping -c 4 192.168.10.10 If both succeed, you have built a functioning Linux inter-VLAN router.
Suppose ns10 sends:
Source:
192.168.10.10
Destination:
192.168.20.20 The source compares the destination against:
192.168.10.0/24 The destination is outside the local subnet.
Therefore:
Destination is remote
↓
Use default gateway
↓
192.168.10.1 The host first needs the gateway’s MAC address.
It performs ARP for:
192.168.10.1 not for:
192.168.20.20 This distinction is extremely important.
The router receives:
SRC 192.168.10.10
DST 192.168.20.20 It checks its routing table.
Because:
192.168.20.0/24 is directly connected through:
rtr0.20 the router forwards the packet there.
It then resolves the destination’s Layer 2 address on VLAN 20 if necessary.
Conceptually:
IP packet
SRC 192.168.10.10
DST 192.168.20.20 remains an IP packet between those endpoints, while the Ethernet headers change as the packet crosses the router.
That distinction between Layer 2 adjacency and Layer 3 end-to-end addressing is foundational networking knowledge.
Run:
sudo ip netns exec ns10 ip neigh You should expect ns10 to know its gateway’s MAC:
192.168.10.1 → router MAC It does not need a direct ARP mapping for:
192.168.20.20 because that host is not on the local Layer 2 network.
Run:
sudo ip netns exec ns-rtr ip neigh The router may know:
192.168.10.10 → MAC on rtr0.10
192.168.20.20 → MAC on rtr0.20 This creates a useful troubleshooting model:
ns10
IP → Gateway MAC
Router
IP → Host MAC on each VLAN Outside the namespaces:
bridge fdb show br br0 Remember the distinction:
ARP / neighbor table
↓
IP → MAC while:
Bridge FDB
↓
MAC → Bridge Port These mechanisms cooperate but solve different problems.
If the bridge/FDB concepts are unfamiliar, revisit Linux Bridge Networking with Network Namespaces and veth.
Start:
sudo ip netns exec ns-rtr \
tcpdump -eni rtr0.10 icmp Then from ns10:
sudo ip netns exec ns10 \
ping 192.168.20.20 You should see ICMP arriving at the router through VLAN 10.
Now open another terminal:
sudo ip netns exec ns-rtr \
tcpdump -eni rtr0.20 icmp You should see the forwarded traffic leave toward VLAN 20.
This gives us direct packet evidence:
rtr0.10
↓
router
↓
rtr0.20 The router-facing parent interface is:
rtr0 Capture:
sudo ip netns exec ns-rtr \
tcpdump -eni rtr0 You can also inspect VLAN-tagged traffic:
sudo ip netns exec ns-rtr \
tcpdump -eni rtr0 vlan This lets you observe the difference between:
rtr0.10
rtr0.20 and the underlying trunk:
rtr0 Packet capture is often the fastest way to determine whether a problem exists at:
VLAN tagging
routing
or return traffic rather than guessing from configuration.
Successful routing does not necessarily mean every VLAN should be allowed unrestricted access to every other VLAN.
Suppose:
VLAN 10 = Application Servers
VLAN 20 = Database Servers You might want:
VLAN 10 → VLAN 20 TCP/5432 but not:
VLAN 10 → every VLAN 20 service This is where routing and firewall policy should be separated conceptually:
Routing
↓
Can Linux reach the network?
Firewall
↓
Should this traffic be permitted? This distinction is crucial.
Inside the router namespace:
sudo ip netns exec ns-rtr \
nft list ruleset Never blindly flush an existing production firewall.
For this isolated lab, we can build a controlled forwarding policy.
Create a table:
sudo ip netns exec ns-rtr \
nft add table inet filter Create a forwarding chain:
sudo ip netns exec ns-rtr \
nft 'add chain inet filter forward { type filter hook forward priority 0; policy drop; }' Allow established and related traffic:
sudo ip netns exec ns-rtr \
nft add rule inet filter forward \
ct state established,related accept For lab testing, permit traffic from VLAN 10 to VLAN 20:
sudo ip netns exec ns-rtr \
nft add rule inet filter forward \
iifname "rtr0.10" oifname "rtr0.20" accept Because return traffic can match the established/related rule, the architecture becomes:
VLAN 10
|
| permitted
v
Router / Firewall
|
v
VLAN 20
VLAN 20
|
unsolicited new connection
|
X
VLAN 10 This demonstrates how routing and security policy can be combined without collapsing the VLAN boundaries.
Run:
sudo ip netns exec ns-rtr \
nft list ruleset Do not assume the rule exists because the command appeared to succeed.
Then test traffic again.
This gives us three independent layers:
Layer 2
VLAN isolation
↓
Layer 3
Routing
↓
Security
Firewall policy A common misunderstanding is:
If VLAN 10 communicates with VLAN 20, I need NAT.
No.
For our topology:
192.168.10.0/24
↓
Router
↓
192.168.20.0/24 the router is directly connected to both networks.
Ordinary routing is sufficient.
NAT becomes relevant in different situations, such as allowing private networks to reach an external network that lacks a return route.
Do not add NAT merely because traffic crosses a router.
Keep these concepts separate.
Routing answers:
Where should this packet go? NAT answers:
Should packet addressing be translated? Firewall filtering answers:
Should this packet be permitted? Therefore:
Routing
≠
NAT
≠
Firewall A well-designed Linux network treats them as separate functions even when one Linux host performs all three.
Test:
sudo ip netns exec ns10 \
ping 192.168.10.1 If this fails, check:
sudo ip netns exec ns10 ip -br link Then:
sudo ip netns exec ns10 ip -br addr Then:
bridge vlan show Check:
veth10-br → VLAN 10
rtr-trunk → VLAN 10 Then capture:
sudo ip netns exec ns-rtr \
tcpdump -eni rtr0.10 arp If ARP never reaches the router, do not troubleshoot IP forwarding yet.
The failure is earlier in the path.
Suppose:
ns10 → 192.168.10.1 = works
ns20 → 192.168.20.1 = works but:
ns10 → ns20 = fails Layer 2 connectivity to the router is functioning.
Now check forwarding:
sudo ip netns exec ns-rtr \
sysctl net.ipv4.ip_forward Expected:
1 Then inspect routes:
sudo ip netns exec ns-rtr ip route Then:
sudo ip netns exec ns-rtr \
ip route get 192.168.20.20 Finally inspect:
sudo ip netns exec ns-rtr \
nft list ruleset This narrows the problem to Layer 3 or firewall forwarding.
Capture:
sudo ip netns exec ns20 \
tcpdump -eni veth20 If the request arrives but no reply returns, inspect ns20:
sudo ip netns exec ns20 ip route It needs a return path.
For this lab:
default via 192.168.20.1 Without that gateway, the router may deliver traffic correctly while the destination cannot return traffic to VLAN 10.
This is classic asymmetric reachability caused by a missing route.
Ask Linux directly:
sudo ip netns exec ns-rtr \
ip route get 192.168.20.20 Expected:
dev rtr0.20 For VLAN 10:
sudo ip netns exec ns-rtr \
ip route get 192.168.10.10 Expected:
dev rtr0.10 If Linux chooses an unexpected path, inspect the complete routing architecture.
CorgiNetwork’s Linux Traffic Uses the Wrong Interface provides the deeper troubleshooting workflow.
Our current router has a simple routing table:
192.168.10.0/24 → VLAN 10
192.168.20.0/24 → VLAN 20 Destination-based routing is sufficient.
But imagine:
VLAN 10 → ISP A
VLAN 20 → ISP B Now the router might need:
Traffic sourced from VLAN 10
↓
Table 100
↓
ISP A
Traffic sourced from VLAN 20
↓
Table 200
↓
ISP B That becomes a policy-routing problem.
Use CorgiNetwork’s Linux Policy Routing with ip rule and Multiple Routing Tables for that architecture.
Suppose the router has:
VLAN 10
VLAN 20
ISP A
ISP B Installing multiple default routes without understanding policy can produce incorrect return paths.
The troubleshooting chain becomes:
VLAN
↓
Router
↓
ip rule
↓
Routing table
↓
Gateway
↓
External interface For persistent Ubuntu implementations, continue with Netplan Multiple Default Routes and Policy Routing.
Multi-interface Linux routers can also encounter reverse-path filtering issues.
Imagine a packet enters:
ISP B while Linux believes the best reverse path to its source is:
ISP A Strict reverse-path validation may interfere.
Do not immediately disable the protection.
First verify the routing design.
CorgiNetwork’s Linux rp_filter and Asymmetric Routing explains strict, loose, and disabled modes and how they interact with multi-homed routing.
ARP does not cross a router in the same way an Ethernet broadcast moves inside a VLAN.
VLAN 10 has its own Layer 2 neighbor relationships:
192.168.10.10
↓
VLAN 10
↓
192.168.10.1 VLAN 20 has:
192.168.20.1
↓
VLAN 20
↓
192.168.20.20 The router joins the two IP networks at Layer 3.
This is why inspecting:
ip neigh inside each namespace is so valuable.
For more complex multi-interface ARP behavior, use CorgiNetwork’s Linux ARP Flux and Multiple NICs on the Same Subnet.
Do not start with:
Ping failed. and randomly modify routes.
Follow the packet.
For traffic:
ns10
↓
ns20 check:
1. ns10 interface
2. ns10 address
3. ns10 route
4. ns10 ARP for gateway
5. VLAN 10 bridge membership
6. router VLAN 10 interface
7. router forwarding
8. router routing decision
9. firewall forwarding
10. router VLAN 20 interface
11. VLAN 20 bridge membership
12. ns20 ARP
13. ns20 route back
14. return traffic That sequence turns a vague networking failure into a finite investigation.
Check namespaces:
ip netns list Check VLAN 10:
sudo ip netns exec ns10 ip -br addr
sudo ip netns exec ns10 ip route
sudo ip netns exec ns10 ip neigh Check VLAN 20:
sudo ip netns exec ns20 ip -br addr
sudo ip netns exec ns20 ip route
sudo ip netns exec ns20 ip neigh Check the router:
sudo ip netns exec ns-rtr ip -br addr
sudo ip netns exec ns-rtr ip route
sudo ip netns exec ns-rtr ip neigh Check forwarding:
sudo ip netns exec ns-rtr \
sysctl net.ipv4.ip_forward Check bridge VLANs:
bridge vlan show Check bridge forwarding:
bridge fdb show br br0 Check firewall:
sudo ip netns exec ns-rtr \
nft list ruleset Finally capture:
sudo ip netns exec ns-rtr \
tcpdump -eni any Host A cannot reach Host B
|
v
Can Host A reach its gateway?
/ \
NO YES
| |
v v
Check VLAN Is forwarding enabled?
and ARP / \
NO YES
| |
v v
Enable Check router
routing route
|
v
Correct interface?
/ \
NO YES
| |
v v
Fix route Check firewall
|
v
Packet reaches
destination?
/ \
NO YES
| |
v v
Capture VLAN Check
traffic return route The same principles scale beyond our laboratory.
For example:
Linux Router
|
+---------------+---------------+
| | |
VLAN 10 VLAN 20 VLAN 30
Servers Database Management
| | |
10.10.10.0/24 10.10.20.0/24 10.10.30.0/24 Firewall policy might allow:
VLAN 10 → VLAN 20
TCP 5432 only while:
VLAN 20 → VLAN 10
new unsolicited sessions denied and:
VLAN 30 → management services
permitted This produces segmentation that is meaningful at both Layer 2 and Layer 3.
A common small-network design is:
Everything
↓
One subnet
↓
One broadcast domain That is easy to configure but provides little segmentation.
A more structured architecture could be:
Users
↓
VLAN 10
Servers
↓
VLAN 20
Management
↓
VLAN 30
IoT
↓
VLAN 40 Then the router/firewall determines which communication paths are actually required.
The goal is not to create VLANs simply because VLANs exist.
The goal is to create meaningful network boundaries.
For laboratories, a router namespace is particularly useful because it gives the virtual router its own:
interfaces
routing table
neighbor table
firewall context
sysctl networking state This separation makes troubleshooting easier.
Instead of mixing:
host management traffic
+
lab routing
+
VLAN gateways inside the default namespace, the routing experiment stays isolated.
A router namespace is not mandatory.
The host itself could own interfaces such as:
br0.10
192.168.10.1
br0.20
192.168.20.1 and route between them.
Conceptually:
VLAN 10
↓
Linux Host
↓
VLAN 20 This can be appropriate in production designs.
The namespace approach is used here because it creates a cleaner educational boundary and makes the router behave like a separate virtual network appliance.
Another design uses one router-facing interface per VLAN:
Router
/ \
| |
VLAN10 VLAN20 rather than:
Router
|
trunk
|
VLAN10 + VLAN20 Both are valid architectures.
The trunk-based model is useful because it demonstrates how one interface can carry multiple tagged VLANs.
VLANs provide Layer 2 separation.
Routing joins different IP networks.
But some environments need separate Layer 3 routing domains as well.
Linux provides Virtual Routing and Forwarding (VRF) for this purpose.
The Linux kernel VRF documentation explains that VRF devices combined with routing rules can create separate routing domains with their own routing tables.
A useful conceptual progression is:
VLAN
↓
Layer 2 separation
Routing
↓
Communication between IP networks
VRF
↓
Layer 3 routing-domain separation CorgiNetwork should treat VRF as its own advanced guide rather than mixing it into ordinary inter-VLAN routing.
First remove the namespaces:
sudo ip netns del ns10
sudo ip netns del ns20
sudo ip netns del ns-rtr Then remove the bridge:
sudo ip link del br0 Verify:
ip netns list and:
ip link show type bridge The lab should now be removed.
Inter-VLAN routing is Layer 3 forwarding between IP networks associated with different VLANs.
A router or Layer 3 routing function connects the networks while the VLANs remain separate Layer 2 domains.
Yes.
Linux contains IP routing functionality and can route between VLAN interfaces when addresses, routes, forwarding, and security policy are configured appropriately.
Not by itself.
A bridge primarily provides Layer 2 forwarding. Routing requires a Layer 3 interface/router function connecting the VLAN networks.
No.
If the router has routes to both networks and endpoints have correct return routes, normal routing is sufficient.
NAT is a separate function.
For an IPv4 Linux system intentionally acting as a router, IPv4 forwarding must be configured appropriately.
For this namespace lab we enable:
sysctl -w net.ipv4.ip_forward=1 inside the router namespace.
Common causes include:
IP forwarding disabled
firewall forwarding blocked
wrong route
missing return route
wrong VLAN configuration Use packet captures on both router interfaces to determine where traffic stops.
Router-on-a-stick is an architecture where one router interface carries multiple VLANs through an 802.1Q trunk and separate VLAN subinterfaces provide Layer 3 gateways.
Our router namespace uses:
rtr0
|
+-- rtr0.10
|
+-- rtr0.20 to demonstrate this design.
Inside the router:
ip route For a specific destination:
ip route get DESTINATION The ip route get interface can resolve a particular route lookup rather than simply displaying the route table.
Check, in order:
interfaces
addresses
VLAN membership
gateway reachability
routes
IP forwarding
firewall
packet capture
return path Avoid changing several layers simultaneously.
Linux inter-VLAN routing connects two concepts that every advanced Linux networking administrator should understand:
Layer 2 segmentation
+
Layer 3 routing The complete architecture from this lab is:
VLAN 10
|
192.168.10.10
|
v
192.168.10.1
Linux Router
192.168.20.1
|
v
192.168.20.20
|
VLAN 20 At Layer 2:
VLAN 10
X
VLAN 20 remains isolated.
At Layer 3:
VLAN 10
↓
Router
↓
VLAN 20 communication becomes possible according to the router’s routes and firewall policy.
The most important diagnostic commands from this project are:
bridge vlan show
bridge fdb show
ip route
ip route get DESTINATION
ip neigh
sysctl net.ipv4.ip_forward
nft list ruleset
tcpdump -eni INTERFACE More importantly, troubleshoot in the correct order.
If the endpoint cannot reach its own gateway, investigate the VLAN and Layer 2 path.
If both endpoints reach their respective gateways but cannot reach each other, investigate forwarding, routing, and firewall policy.
If traffic reaches the destination but replies disappear, investigate the return route.
If a multi-homed router chooses an unexpected uplink, move into policy routing rather than trying to solve the problem with arbitrary route metrics.
This article extends the CorgiNetwork advanced Linux networking cluster:
Linux Network Namespaces Explained establishes isolated network stacks and virtual interfaces.
Linux Bridge Networking with Network Namespaces and veth adds virtual Layer 2 switching.
Linux Bridge VLAN Filtering Explained divides that switch into separate VLAN broadcast domains.
This guide adds Layer 3 routing between those VLANs.
For more complex route selection, Linux Policy Routing with ip rule and Multiple Routing Tables adds multiple routing tables and source-based policies.
For Ubuntu multi-gateway deployments, Netplan Multiple Default Routes and Policy Routing provides the persistent configuration layer.
When reverse paths become asymmetric, Linux rp_filter and Asymmetric Routing covers reverse-path validation.
When Linux selects an unexpected interface, Linux Traffic Uses the Wrong Interface provides the diagnostic workflow.
And for multi-interface ARP anomalies, Linux ARP Flux and Multiple NICs on the Same Subnet covers the Layer 2 neighbor-resolution side.
The resulting CorgiNetwork learning path is now:
Network Namespaces
↓
veth
↓
Linux Bridge
↓
VLAN Filtering
↓
Inter-VLAN Routing
↓
Firewall Policy
↓
Policy Routing
↓
Multiple Gateways
↓
Advanced Linux Routing The next article in this cluster should move naturally to Linux nftables Inter-VLAN Firewall: Build Stateful Firewall Rules Between VLANs, where we will turn the virtual router into a controlled firewall and build practical policies for server, management, database, and untrusted VLANs.
AI Agents as Code: Managing AI Agent Configurations, Prompts and Tools with Git AI agents…
Data Center Power Distribution Architecture: UPS, PDU, Redundancy and Rack Power Explained Data center power…
DNSSEC Troubleshooting: Fix SERVFAIL & DNSKEY Errors DNSSEC Troubleshooting: How to Fix SERVFAIL, DS, DNSKEY…
Terraform State Management Explained: Remote State, Locking, Backends and Best Practices Terraform makes infrastructure reproducible…
RTO vs RPO Explained: Disaster Recovery Objectives, Examples and Planning A disaster recovery plan is…
Network Intrusion Detection Explained: NIDS, Sensors, Signatures and Traffic Analysis A firewall can decide whether…