Skip to content

Instantly share code, notes, and snippets.

@tmo1
Created October 27, 2025 01:37
Show Gist options
  • Select an option

  • Save tmo1/818ab546920cf16ae178a4af7410eee1 to your computer and use it in GitHub Desktop.

Select an option

Save tmo1/818ab546920cf16ae178a4af7410eee1 to your computer and use it in GitHub Desktop.
VPN bypass via a SOCKS proxy and `SO_BINDTODEVICE`

VPN bypass via a SOCKS proxy and SO_BINDTODEVICE

The problem

On a desktop system configured to pass all traffic through a VPN (for privacy or other reasons), it is often necessary or desirable to route some traffic directly, avoiding the VPN, for a variety of reasons, including the following:

  • Some websites will not accept connections coming through a VPN
  • The VPN may reduce throughput or add latency, which may be unacceptable or undesirable in some circumstances.

Bypassing the VPN as an ordinary user is surprisingly difficult to do, for at least two reasons:

  • Routing is typically configured in advance and handled by the kernel, and is not designed to be managed on an ongoing basis by the user.
  • Routing is typically done based on IP address, and VPN bypass decisions will typically depend on DNS addresses.

The latter issue can be addressed via policy based routing, which is typically performed on a router (e.g., OpenWrt). The standard general purpose solution is dnsmasq with its --ipset option (implemented by Jason A. Donenfeld, author of WireGuard), but it can be somewhat complicated to configure. This article offers a simpler solution that does not require any scripting or modifications to the system's networking configuration.

The SO_BINDTODEVICE SOCKS proxy solution

On Linux, a very simple method of VPN bypass is available (this has been tested for WireGuard, but should work for other types of VPNs implemented via IP tunnel interfaces): The Linux kernel provides the SO_BINDTODEVICE socket option, which enables forcing a connection to use a specific network interface, bypassing normal routing. For kernel versions prior to 5.7, using SO_BINDTODEVICE required CAP_NET_RAW and was thus only available to root users, but from version 5.7 it is available to any user.

By using SO_BINDTODEVICE, software can bypass a VPN by binding directly to the real network interface. Most common network software, including web browsers, cannot be configured to use SO_BINDTODEVICE (a notable exception is curl, whose --interface <name> option exposes SO_BINDTODEVICE functionality to the user); one solution is to use a proxy that can be so configured. Most common proxies as well cannot be configured to use SO_BINDTODEVICE (Tinyproxy has an open issue from more than six years ago and two unmerged PRs (#494, #565 regarding SO_BINDTODEVICE; microsocks has a couple of unmerged PRs (#29, #79) going back more than five years; asyncio-socks-server has an open issue about it going back more than three years), but there is one SOCKS proxy whose entire raison d'être is to implement SO_BINDTODEVICE: Dario Ostuni's Soks. It hasn't been touched for five years, but it's a dead simple program (a single C file consisting of just 270 LOC), and very easy to build. Just ensure that git and a standard build environment are present, and do:

~$ git clone https://github.com/dariost/soks.git
~$ cd soks
~$ make

To run the proxy, do:

~$ ./soks -i <interface> -p <port>

where <interface> is your real interface name (e.g. eth0, wlan0, or a modern udev "predictable" name), and <port> is any available port.

The VPN can now be bypassed by connecting through the proxy; for regular traffic that should go through the VPN, simply connect as before without using the proxy. To verify that things are working properly, do:

~$ curl https://ifconfig.co/
~$ curl --socks5 localhost:<port> https://ifconfig.co/

The former command should return your VPN IP address, and the latter should return your real (ISP) IP address.

Once things have been verified to be working properly, the proxy can be run in the background and its output suppressed via the standard Linux shell syntax:

~$ ./soks -i <interface> -p <port> > /dev/null &

Configuring Firefox to selectively use the proxy

There are obviously many ways to configure applications to selectively use the proxy; the following applies to the Firefox web browser.

To configure Firefox to use the proxy, go to Settings / General / Network settings, click Settings, select Manual proxy configuration, then set SOCKS Host to localhost and the accompanying Port to the port number soks is using, and select SOCKS v5. (Proxy DNS when using SOCKS v5 is optional.)

Following are some methods for selectively using the proxy:

  • Create two browser profiles, one configured to use the proxy and one not configured to do so.
  • Using the Firefox Multi-Account Containers extension, per-container proxy settings can be set. To configure a container to use soks, click the Firefox Multi-Account Containers extension icon and go to a particular container's settings via either Manage Containers or Manage This Container, click on Advanced proxy settings and set it to socks://localhost:<port>. To configure a website to always open in that container, navigate to the site, click the Firefox Multi-Account Containers extension icon, click Always Open This Site in... and select the appropriate container.
  • A proxy management extension such as Foxy Proxy can be used to easily switch proxy settings, either manually, or automatically based on URLs.

Other VPN bypass methods

In addition to the dnsmasq with --ipset option mentioned above, following are some other VPN bypass methods:

  • Run any HTTP(S) or SOCKS proxy on a different host that is not configured to route traffic over the VPN, and selectively connect through that host / proxy as above. One obvious downside to this approach is that it will not easily work for something like a laptop that is often moved between networks.
  • Run the VPN inside a virtual machine or container, rather than on the host system. Use the VM / container for traffic that should traverse the VPN, and the host system (or another VM / container) for traffic that shouldn't (see this thread) An obvious downside to this approach is the added complexity and overhead involved.
  • Use Linux network namespaces: there are many guides online to do this (e.g. here, here, here, and here), but they are generally rather complicated and involve low-level changes to the system networking configuration.
  • Use SO_BINDTODEVICE with any software via LD_PRELOAD instead of a proxy: see here and here.
  • ocproxy / vpnns: ocproxy can be used to "[route] traffic from different applications/browsers through different VPNs (or no VPN)" and "vpnns isolates VPN-related network traffic and applications inside a separate network namespace. Applications intended to be used with the VPN cannot bypass the VPN to access the internet directly, and applications not intended to be used with the VPN cannot send traffic through the VPN tunnel." These tools have not been updated in eight years; I have no experience with them; and I do not know if they support WireGuard.
  • wireproxy: "wireproxy is a completely userspace application that connects to a wireguard peer, and exposes a socks5/http proxy or tunnels on the machine. This can be useful if you need to connect to certain sites via a wireguard peer, but can't be bothered to setup a new network interface for whatever reasons." Hacker News discussion. This is basically the opposite of the solution in this guide. A major downside is that it uses a third-party, userspace reimplementation of upstream's kernel implementation of WireGuard.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment