How to Rewrite Destination IP in iptables Without DNAT: A Mangle Table Solution


4 views

We often encounter legacy applications hardcoded to connect to internal IP addresses (e.g., 192.168.251.3) when they should be using public IPs (1.2.3.4). This becomes particularly problematic when:

  • The application cannot be modified
  • DNAT fails because the target IP isn't local
  • You need to maintain original connection tracking

The mangle table in iptables allows packet header modification before routing decisions are made. Unlike DNAT which requires local IPs, mangle can rewrite destinations regardless of interface configuration.

# Enable IP forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward

# Route packets through mangle table
iptables -t mangle -A PREROUTING -d 192.168.251.3 -j MARK --set-mark 1
iptables -t mangle -A PREROUTING -d 192.168.251.3 -j TTL --ttl-set 64

# Create custom routing
ip rule add fwmark 1 lookup 100
ip route add default via 1.2.3.4 table 100

# Alternative method using ROUTE target (requires xtables-addons)
iptables -t mangle -A PREROUTING -d 192.168.251.3 -j ROUTE --gw 1.2.3.4

After applying these rules:

# Check packet flow
tcpdump -i any host 192.168.251.3 or host 1.2.3.4

# Verify routing
ip route show table 100
ip rule list

For an application connecting to MongoDB at 192.168.1.100 that moved to AWS (3.4.5.6):

iptables -t mangle -A OUTPUT -d 192.168.1.100 -p tcp --dport 27017 \\
  -j TPROXY --on-port 27017 --on-ip 3.4.5.6 --tproxy-mark 0x1/0x1
  • Missing kernel modules (install xtables-addons)
  • Forgetting to enable IP forwarding
  • Not considering return traffic routing
  • Firewall rules blocking redirected traffic

Recently, I encountered an interesting networking challenge where a legacy application stubbornly tries to connect to a server's private IP address (192.168.251.3) which isn't reachable from the client machines. While the server's public IP (1.2.3.4) is perfectly accessible, modifying the application itself wasn't an option.

The obvious solution would be Destination NAT (DNAT), but this approach fails because:

  • The target IP (1.2.3.4) isn't locally assigned to any client interface
  • DNAT requires the new destination to be routable from the NAT gateway itself
  • We need packet modification before routing decisions are made

The solution lies in using iptables' mangle table with MARK and routing rules. Here's the complete approach:

# First, mark packets destined for the private IP
iptables -t mangle -A PREROUTING -d 192.168.251.3 -j MARK --set-mark 1

# Then create a custom routing table
echo "100 custom_route" >> /etc/iproute2/rt_tables

# Add route for marked packets
ip rule add fwmark 1 table custom_route
ip route add default via 1.2.3.4 table custom_route

# Ensure proper routing (may need to adjust based on your setup)
iptables -t nat -A POSTROUTING -j MASQUERADE

For more complex scenarios (like transparent proxying), TPROXY can be considered:

# Enable routing and TPROXY
sysctl -w net.ipv4.ip_forward=1
iptables -t mangle -A PREROUTING -d 192.168.251.3 -p tcp --dport [PORT] -j TPROXY --on-port [PORT] --on-ip 1.2.3.4 --tproxy-mark 0x1/0x1

# Routing setup remains similar to previous example
ip rule add fwmark 1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100

To verify the solution is working:

# Check packet marking
iptables -t mangle -L -n -v

# Examine routing decisions
ip route show table custom_route

# Test connectivity with (replace port as needed):
telnet 192.168.251.3 [PORT]

Common issues to watch for:

  • Make sure reverse path filtering isn't blocking the packets (sysctl net.ipv4.conf.all.rp_filter)
  • Verify that the MASQUERADE rule is correctly applied if NAT is needed
  • Check that the custom routing table has higher priority than main table

While this solution works, be aware of:

  • Additional CPU overhead from packet marking and routing lookups
  • Potential complications with connection tracking
  • Need for persistent rules (consider using iptables-persistent or equivalent)