How to Force Local IP Traffic Through External Network Interface in Linux: A Complete iptables and Routing Guide


2 views



When developing network applications or testing interface drivers, we often need to simulate traffic between two interfaces on the same machine. The specific scenario involves:

  • eth1 with IP 192.168.1.1
  • eth2 with IP 192.168.2.2
  • A crossover cable connecting eth1 and eth2

The goal is to force all traffic destined for 192.168.1.1 to physically traverse eth2, then return via eth1, rather than being handled internally by the loopback interface.

Standard Linux routing handles local traffic intelligently - when you access a local IP, the kernel shortcuts the network stack and processes it internally. Our challenge is to override this behavior.

# This doesn't solve our problem
ip route add 192.168.1.1 dev eth2

We need a combination of routing rules and NAT to achieve our goal:

# First, add a special routing table
echo "200 special" >> /etc/iproute2/rt_tables

# Route traffic to our alternate IP via eth2
ip route add 192.168.1.100 dev eth2 table special
ip rule add from all to 192.168.1.100 lookup special

# Set up NAT to translate our alternate IP to the real one
iptables -t nat -A PREROUTING -d 192.168.1.100 -j DNAT --to-destination 192.168.1.1
iptables -t nat -A POSTROUTING -s 192.168.1.1 -j SNAT --to-source 192.168.1.100

# Enable routing between interfaces
sysctl -w net.ipv4.ip_forward=1

To test this setup with an HTTP server:

# On one terminal:
python3 -m http.server 80

# On another terminal:
curl http://192.168.1.100

Use tcpdump to verify the traffic path:

tcpdump -i eth2 -n host 192.168.1.100
tcpdump -i eth1 -n host 192.168.1.1

If you're specifically testing network drivers, consider creating virtual interfaces:

# Create virtual Ethernet pair
ip link add veth0 type veth peer name veth1

# Assign IPs
ip addr add 192.168.3.1/24 dev veth0
ip addr add 192.168.3.2/24 dev veth1

# Bring them up
ip link set veth0 up
ip link set veth1 up

For Windows users, you can achieve similar functionality using the routing table:

route add 192.168.1.100 mask 255.255.255.255 192.168.2.2 if 

And using PowerShell for NAT:

Add-NetNatStaticMapping -NatName "LoopbackTest" -Protocol TCP -ExternalIPAddress 192.168.1.100 -InternalIPAddress 192.168.1.1 -InternalPort 80 -ExternalPort 80


When developing network drivers or testing interface behavior, we often need to route local traffic through physical interfaces instead of the loopback adapter. Here's a concrete scenario:

# Current interface configuration
eth1: 192.168.1.1/24
eth2: 192.168.2.2/24

The Linux kernel automatically routes traffic destined for local addresses (like 192.168.1.1) through the loopback interface. Simple routing commands won't override this behavior:

# This won't work as expected for local addresses
ip route change 192.168.1.1/24 dev eth2

We need to combine several techniques to achieve the desired traffic flow:

First, mark packets destined for our target IP:

iptables -t mangle -A OUTPUT -d 192.168.1.1 -j MARK --set-mark 1

Then create a custom routing table for marked packets:

echo "100 custom" >> /etc/iproute2/rt_tables
ip rule add fwmark 1 table custom
ip route add default via 192.168.2.1 dev eth2 table custom

Finally, configure NAT to handle the return traffic:

iptables -t nat -A POSTROUTING -o eth2 -j MASQUERADE

Here's a full script to implement this solution:

#!/bin/bash

# Configure interfaces
ip addr add 192.168.1.1/24 dev eth1
ip addr add 192.168.2.2/24 dev eth2

# Mark packets destined for 192.168.1.1
iptables -t mangle -A OUTPUT -d 192.168.1.1 -j MARK --set-mark 1

# Set up custom routing
echo "100 custom" >> /etc/iproute2/rt_tables
ip rule add fwmark 1 lookup custom
ip route add default via 192.168.2.1 dev eth2 table custom

# Enable NAT
iptables -t nat -A POSTROUTING -o eth2 -j MASQUERADE

# Verify with:
# ip route show table custom
# iptables -t mangle -L -n -v

To verify this works with actual services:

# Set up test HTTP server
python3 -m http.server --bind 192.168.1.1 80 &

# Make request through forced path
curl --interface eth2 http://192.168.1.1

# For iperf testing:
iperf3 -s -B 192.168.1.1 &
iperf3 -c 192.168.1.1 --bind 192.168.2.2

If you're using a crossover cable between interfaces, you can simplify the setup:

# Assign addresses on different subnets
ip addr add 192.168.1.1/24 dev eth1
ip addr add 192.168.1.2/24 dev eth2

# Add explicit route
ip route add 192.168.1.1/32 dev eth2

# Disable reverse path filtering
sysctl -w net.ipv4.conf.all.rp_filter=0
sysctl -w net.ipv4.conf.eth1.rp_filter=0
sysctl -w net.ipv4.conf.eth2.rp_filter=0