I've built a VPN app that is based on wireguard on macOS (I have both AppStore ver. and Developer ID ver). I want to achieve split tunneling function without changing the system route table.
Currently, I'm making changes in PacketTunnelProvider: NEPacketTunnelProvider. It has included/excluded routes that function as a split tunnel, just that all changes are immediately reflected on the route table: if I run
netstat -rn
in terminal, I would see all rules/CIDRs I added, displayed all at once. Since I have a CIDR list of ~800 entries, I'd like to avoid changing the route table directly.
I've asked ChatGPT, Claude, DeepSeek, .etc. An idea was to implement an 'interceptor' to
intercept all packets in packetFlow(_:readPacketsWithCompletionHandler:), extract the destination IP from each packet, check if it matches your CIDR list, and either reinject it back to the system interface (for local routing) or process it through your tunnel.
Well, LLMs could have hallucinations and I've pretty new to macOS programming. I'm asking to make sure I'm on the right track, not going delusional with those LLMs :) So the question is, does the above method sounds feasible? If not, is it possible to achieve split tunneling without changing the route table?
Your request is strange: You want to change how the system routes packets without modifying the routing table, but the routing table is how the system determines how to route packets. How are you expecting that to work? [1]
Regarding this:
reinject it back to the system interface (for local routing)
NEPacketTunnelFlow
doesn’t support re-injecting packets. If a packet is routed to your provider, you are responsible for getting it to its destination.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] There’s an old joke that macOS must have two of everything (-: In many respects that’s true, but not here. There’s only one mechanism to route packets based on their source and destination addresses.