How to exclude a specific destination IP from VPN?


Question

I want to route a specific IP address from hotspot (wlan0) to Mobile Data (rmnet_data1) interface on Android 11, bypassing VPN.


I use my phone as a hotspot and also with the help of an app called "VPN Hotspot" I share the VPN with my PC. I want to exclude a specific IP address from VPN. I have tried the route option in "OpenVPN for Android" client and it doesn't seem to work properly. I think it might because of "Always On" enabled.


Can I achieve this with ip route command?


Answer

Note: The commands given below require a rooted device.




Whenever you enable or disable VPN on Android device, routing table is recreated by the Android framework. There can be a score of rules added to the routing table when VPN is enabled, since it does filtering based on UIDs and Socket Marks. So you need to manipulate the routing table depending on the existing rules.


ROUTING POLICY AND TABLES


From the info you provided, I'm quoting the relevant parts:


~# ip -4 rule
...
11000: from all iif tun0 lookup local_network
...
17900: from all iif wlan0 lookup tun0
18000: from all iif wlan0 lookup rmnet_data1
...

~# ip -4 route show table all
...
192.168.18.0/24 dev wlan0 table local_network...
...
default via 21.62.249.109 dev rmnet_data1 table rmnet_data1...
21.62.249.104/29 dev rmnet_data1 table rmnet_data1...
...

~# iptables -t nat -S
...
-A POSTROUTING -j tetherctrl_nat_POSTROUTING
-A tetherctrl_nat_POSTROUTING -o tun0 -j MASQUERADE
-A tetherctrl_nat_POSTROUTING -o rmnet_data1 -j MASQUERADE
...

In simple words what the above rules state:



  • Everything coming at VPN interface (tun0) is routed to hotspot interface (wlan0) if the destination IP is within hotspot subnet (192.168.18.0/24) i.e. the packets don't belong to the apps running on device (open sockets).

  • Everything coming at hotspot interface is routed to VPN interface.

  • The packets which don't qualify to be routed through VPN (mainly the VPNed traffic itself, any excluded apps, or the local packets) are routed to Mobile Data interface (rmnet_data1).

    21.62.249.109 is your IP assigned by the carrier and 21.62.249.104/29 is their gateway.

  • All packets going out through VPN and/or Mobile Data interfaces are MASQUERADEd i.e. their source IP address is changed from local IP to a routable IP address (21.62.249.109 in your case).


So this is the simplified flow we are interested in:


Hotspot -> Routing -> FORWARD -> SNAT -> Internet
Internet -> Conntrack -> Routing -> FORWARD -> Hotspot



SOLUTION


After understanding how it works, lets route it the way we want. Say a host on hotspot network is connecting to 1.2.3.4 through internet. But you don't want to send this specific traffic through VPN on Android device.



  • Define a rule in RPDB and a routing table to route the traffic coming at hotspot interface to Mobile Data interface, if it's destined for 1.2.3.4:


    ~# ip rule add iif wlan0 lookup 9000 pri 9999
    ~# ip route add 1.2.3.4 dev rmnet_data1 table 9000

    I used RPDB priority 9999 and table 9000. You might need to change the priority (so that your rule is always on top) and table number. Check /data/misc/net/rt_tables for reserved table numbers which you should not use.



  • Define another rule to route the packets coming from 1.2.3.4 at Mobile Data interface to hotspot interface (if the destination IP is within hotspot subnet):


    ~# ip rule add from 1.2.3.4 iif rmnet_data1 lookup local_network pri 9999

    Note that I used here an already existing routing table local_network. You can define your own too.



  • All outgoing packets through Mobile Data interface are already MASQUERADEd as explained above, so we don't need to add another iptables rule. If needed, you can also use SNAT target in place of MASQUERADE.



  • Make sure that IP forwarding is enabled and allowed in firewall. Usually it's not needed because Wi-Fi hotspot already sets this up for you:


    ~# echo 1 > /proc/sys/net/ipv4/ip_forward
    ~# iptables -I FORWARD -o rmnet_data1 -d 1.2.3.4 -j ACCEPT
    ~# iptables -I FORWARD -i rmnet_data1 -s 1.2.3.4 -j ACCEPT



To delete these rules use ip rule del, ip route del and iptables -D.




NOT WORKING?


If the above rules don't work, or you need to adapt the rules for a different situation, iptables -j LOG is a good friend for troubleshooting.


For reference read any Linux documentation or guides about IP routing and Netfilter. It's not specifically about Android.




RELATED:



Topics


2D Engines   3D Engines   9-Patch   Action Bars   Activities   ADB   Advertisements   Analytics   Animations   ANR   AOP   API   APK   APT   Architecture   Audio   Autocomplete   Background Processing   Backward Compatibility   Badges   Bar Codes   Benchmarking   Bitmaps   Bluetooth   Blur Effects   Bread Crumbs   BRMS   Browser Extensions   Build Systems   Bundles   Buttons   Caching   Camera   Canvas   Cards   Carousels   Changelog   Checkboxes   Cloud Storages   Color Analysis   Color Pickers   Colors   Comet/Push   Compass Sensors   Conferences   Content Providers   Continuous Integration   Crash Reports   Credit Cards   Credits   CSV   Curl/Flip   Data Binding   Data Generators   Data Structures   Database   Database Browsers   Date &   Debugging   Decompilers   Deep Links   Dependency Injections   Design   Design Patterns   Dex   Dialogs   Distributed Computing   Distribution Platforms   Download Managers   Drawables   Emoji   Emulators   EPUB   Equalizers &   Event Buses   Exception Handling   Face Recognition   Feedback &   File System   File/Directory   Fingerprint   Floating Action   Fonts   Forms   Fragments   FRP   FSM   Functional Programming   Gamepads   Games   Geocaching   Gestures   GIF   Glow Pad   Gradle Plugins   Graphics   Grid Views   Highlighting   HTML   HTTP Mocking   Icons   IDE   IDE Plugins   Image Croppers   Image Loaders   Image Pickers   Image Processing   Image Views   Instrumentation   Intents   Job Schedulers   JSON   Keyboard   Kotlin   Layouts   Library Demos   List View   List Views   Localization   Location   Lock Patterns   Logcat   Logging   Mails   Maps   Markdown   Mathematics   Maven Plugins   MBaaS   Media   Menus   Messaging   MIME   Mobile Web   Native Image   Navigation   NDK   Networking   NFC   NoSQL   Number Pickers   OAuth   Object Mocking   OCR Engines   OpenGL   ORM   Other Pickers   Parallax List   Parcelables   Particle Systems   Password Inputs   PDF   Permissions   Physics Engines   Platforms   Plugin Frameworks   Preferences   Progress Indicators   ProGuard   Properties   Protocol Buffer   Pull To   Purchases   Push/Pull   QR Codes   Quick Return   Radio Buttons   Range Bars   Ratings   Recycler Views   Resources   REST   Ripple Effects   RSS   Screenshots   Scripting   Scroll Views   SDK   Search Inputs   Security   Sensors   Services   Showcase Views   Signatures   Sliding Panels   Snackbars   SOAP   Social Networks   Spannable   Spinners   Splash Screens   SSH   Static Analysis   Status Bars   Styling   SVG   System   Tags   Task Managers   TDD &   Template Engines   Testing   Testing Tools   Text Formatting   Text Views   Text Watchers   Text-to   Toasts   Toolkits For   Tools   Tooltips   Trainings   TV   Twitter   Updaters   USB   User Stories   Utils   Validation   Video   View Adapters   View Pagers   Views   Watch Face   Wearable Data   Wearables   Weather   Web Tools   Web Views   WebRTC   WebSockets   Wheel Widgets   Wi-Fi   Widgets   Windows   Wizards   XML   XMPP   YAML   ZIP Codes