Routing is at IP layer 3. TCP is at layer 4, so routing alone isn't enough to deal with this.
In short: the interesting traffic has to be tagged with iptables
, and tagged packets selected with ip rule
's fwmark
to use a separate routing table. Then two more fixes have to be applied for the locally initiated/receiving traffic case, which is more difficult than the routed case. All the settings are of course done on the local system.
Routing table 80
(a matching symbolic name can be added in /etc/iproute2/rt_tables
but that's not mandatory) and mark 0x80
were "arbitrarily" chosen.
ip route add table 80 192.168.1.0/24 dev eth0 scope link src 192.168.1.56
ip route add table 80 default dev eth0 via 192.168.1.1
Using -I
to ensure that the iptables rules aren't appended too late. You should check with your current rules how to reorder that if needed:
iptables -t mangle -N markports
iptables -t mangle -I PREROUTING 1 -j CONNMARK --restore-mark
iptables -t mangle -I OUTPUT 1 -m mark --mark 0 -j markports
iptables -t mangle -I OUTPUT 2 -j CONNMARK --save-mark
iptables -t mangle -A markports -p tcp --dport 80 -j MARK --set-mark 0x80
iptables -t mangle -A markports -p tcp --dport 443 -j MARK --set-mark 0x80
ip rule add fwmark 0x80 lookup 80
This blog: Netfilter Connmark ยป To Linux and beyond ! gives good informations on CONNMARK
.
This should have been working, but actually the wrong default outgoing IP will have been selected at the first routing decision, because the route was about to be through tun0
. At the reroute check made because of the mangle/OUTPUT
's mark (see this Packet flow in Netfilter and General Networking schematic for clarification), this IP won't change. If the traffic handled was routed instead of being locally initiated, this problem wouldn't happen (using a separate net namespace to ensure this is a solution for services, probably not for a Desktop). So this requires also a layer of MASQUERADE
(or SNAT
for more complex cases) on top of it:
iptables -t nat -I POSTROUTING 1 -m mark --mark 0x80 -j MASQUERADE
Now that the outgoing source IP is correct, it's still not working: the reverse path filter triggers in the return path for about the same reason: the routing decision made before PREROUTING
doesn't know the fwmark
for now (despite the previous schematic placing mangle/PREROUTING
before the routing decision, that's apparently not the case), thus considers the return traffic packets to be spoofed and drops them early. The eth0
interface's rp_filter
has to be put in loose mode for this to be allowed. This might have some (very minor behind NAT) security issues but I found it unavoidable for this non-routed case:
echo 2 > /proc/sys/net/ipv4/conf/eth0/rp_filter
You'll have to find how to set it permanently (eg echo net.ipv4.conf.eth0.rp_filter=2 > /etc/sysctl.d/90-local-loose-mode.conf
if nothing else alters it later).
Tested ok using namespaces with similar settings to OP's.
NOTE: DNS requests will still go through the tunnel. Some geolocalized web services might not work as expected.
Best Answer
Since you haven't provided more information, I am unsure how to best answer.
So here goes my attempt at giving you some directions, willfully ignoring that you can't modify your routing table (you will understand why reading my suggestion):
Depending on the VPN client and where it hooks into the FIB (forward information base) of the kernel, you might have some luck in that the monitoring of the FIB, or, using your expression routing table, by the VPN only happens for the
local
andmain
rule tables. You can check your routing rules usingFor each of the strings behind the tag "lookup" (which are the rule table entries), you can query the according routing information from the FIB, using
With a bit of luck you could try to construct a rule that matches your requirements and give it preference in the rule lookup table. For example (I made something up to give you a head-start), let's add a new rule with higher preference than
main
to certain flows:On a standard Linux (Ubuntu in the case of this post) system, you'd be seeing the three default rule tables
local
,main
anddefault
, of which you normally only see themain
table when invokingnetstat -rn
for example.Now we want to populate the FIB entries in lookup table 888 with a new routing entries:
Let's see how our routing entries in table 888 look like:
I think you get the idea. Now, with regard to your specific routing needs, it's unclear as to what exactly you're trying to achieve. Make sure that you flush the routing cache when toying with rule tables:
Note, that using the iproute2 architecture you can basically filter and modify virtually any FIB entry; rule entries can even be made based on fwmarks and/or u32 classifiers, like follows (example taken from the policy routing book):
For things go haywire in rule tables entries, many years ago I had prepared a small bash snippet to put my system back to original routing rules state:
Surprisingly, it seems that after over 10 years of
iproute2
's existence, still only few people seem to know that there is an universe beyond classical "broken" tools likeifconfig
ornetstat
.