Per-tab proxy routing in Chrome

I route some traffic through a SOCKS5 proxy but not all of it. Video calls and streaming get slow if they go through the proxy, and most browsing doesn’t need it. I wanted to pick which tabs get proxied without switching the whole browser back and forth.

Chrome’s proxy API works at the browser level through PAC scripts. No per-tab support (SwitchyOmega has had open issues about this since 2017). But it can route by domain, which is close enough. I built an extension around that.

PAC script routing

Right-click a page, select “Route this tab through proxy.” The extension grabs the hostname, adds it to a list, and regenerates the PAC script:

Context menu

function FindProxyForURL(url, host) {
  if (dnsDomainIs(host, "app.example.com") || host === "app.example.com") {
    return "SOCKS5 localhost:1080";
  }
  return "DIRECT";
}

“Per-tab” is really per-domain. Two tabs on the same host both get routed. Chrome API limitation.

There’s also an all-traffic mode that sends everything through the proxy, and a toggle to turn it off entirely.

Popup

Control API

The proxy address is configurable in settings. Optionally you can point it at an HTTP API that handles connect/disconnect (for example if the proxy lives in a Docker container you start and stop):

GET  /status     → {"connected": true}
POST /connect    → {"auth_url": "https://..."} or {"connected": true}
POST /disconnect → {"disconnected": true}

With an API configured, the popup gets Connect/Disconnect buttons and polls for status. Without it, the extension is just a proxy switcher.

IPv6 gotcha

Chrome resolves DNS on the proxy side for SOCKS5. If the proxy returns IPv6 addresses first and the network is IPv4-only, connections fail silently with ERR_SOCKS_CONNECTION_FAILED. Took a while to figure out. Fix: precedence ::ffff:0:0/96 100 in /etc/gai.conf on the proxy host.

Repo: github.com/vfmatzkin/socks-tab-router