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)