Application / interface specific routing

macosnetworkingroutingunixvpn

I have a routing configuration whose intention is to route all traffic through a tunnel (utun3) into an openvpn client which then connects to the openvpn server (64.120.44.114) via my physical interface (en0).

What I want to do is route certain applications directly via the physical interface (en0) instead of through the tunnel (utun3). Currently, the routing pair 0/1 and 128.0/1 is forcing all internet traffic through the tunnel.

What I had imagined is that if I were to send traffic to an internet address from an application that binds its outgoing address on the physical interface (ie. 10.0.1.15), then this traffic will route through the remaining default route for that address' interface (en0). Unfortunately, the operating system simply fails to route these packets instead:

$ ping 8.8.8.8          # or mtr 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: icmp_seq=0 ttl=55 time=40.835 ms

$ ping -b utun3 8.8.8.8 # or mtr -a 10.12.44.16 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: icmp_seq=0 ttl=55 time=42.036 ms

$ ping -b en0 8.8.8.8   # or mtr -a 10.0.1.15 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: sendto: No route to host

Is there something I can do to ensure that packets sent with an outgoing address that is incompatible with the overriding route 0/1, 128.0/1 to instead use the compatible default route? If not, is there some other means of configuring the routing table that might serve the purpose of routing through en0 for specific applications but routing through utun3 "by default"? If there is no such method with respect to routing, can you recommend any software solutions for doing application-specific routing or ignoring the routing table? For my case, ideally the software should run on macOS.

I have the following interfaces:

en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    ether 60:03:08:8b:96:88
    inet6 fe80::1c29:1e13:e8ba:fd3%en0 prefixlen 64 secured scopeid 0x5
    inet 10.0.1.15 netmask 0xffffff00 broadcast 10.0.1.255
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect
    status: active
utun3: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500
    inet 10.12.44.16 --> 10.12.44.16 netmask 0xfffffc00

I have the following in my routing table:

default            10.0.1.1           UGSc            0        0     en0
10.0.1.1           0:24:36:a0:86:a5   UHLWIir         4      106     en0
64.120.44.114/32   10.0.1.1           UGSc            1        0     en0

0/1                10.12.44.1         UGSc          101        2   utun3
128.0/1            10.12.44.1         UGSc           12        0   utun3
10.12.44/22        10.12.44.16        UGSc            1        0   utun3
10.12.44.16        10.12.44.16        UH              3      170   utun3

Best Answer

Linux routing is VERY versatile, and there are many ways of doing what you want. But you will rise the complexity of the setup (read: it will get harder to troubleshoot when/if you get a problem)

The easiest way, in my opinion, is by using a rule to implement routing based on source of packets. (Sometimes called policy based routing, by cisco anyway)

Linux supports multiple route tables, and it's easy to add custom tables (read iproute2 manual)

Then, it supports rules to direct "route searching" to different tables based on priority and criteria. Rule criterium can be source address. By default there are 3 tables: local, main and default. You can:

1-create a new table, say "direct"

    echo "100 direct" >> /etc/iproute2/rt_tables

2-add a default route to that table via en0

    ip route add default dev en0 table direct

3-add a new rule with, e.g., priority 1000 to use table direct for traffic originating in 10.0.1.15

    ip rule add from 10.0.1.15 lookup direct

Now, the issue is that MacOS is not linux and I came to know that your issue is in MacOS, which I'm not very versed on. Nevertheless, it seems doable. Current MacOS comes with PF, a firewall ported from OpenBSD aparently at its version 4.5 stage. This firewall is able to modify -at the kernel level- the routing based on rules (on top of doing a lot of other things). You specify this using a route-to keyword in rules. Rules are usually specified in anchors which contain pieces of configuration. PF comes disabled by default, so you will need to enable it too. You could:

1- Create a new anchor config file /etc/pf.anchors/pbr.conf with:

     pass out on utun3 route-to (en0 10.0.1.1) from utun3 to any

that will redirect outgoing traffic through utun3 to en0 if the source was an utun3 address.

2- Add this anchor to the pf config editing /etc/pf.conf and putting

    anchor "pbr.rules"
    load anchor "pbr.rules" from "/etc/pf.anchors/pbr.rules"

3- Enable PF somehow, to do testing you can use pfctl -E and afterwards you can use pfctl -F all -f /etc/pf.conf to reload the configuration if needed.

Look Run command on startup / login (Mac OS X)