mullvad / browser-extension

Mullvad Browser Extension improves your browsing experience while using Mullvad VPN.
Other
192 stars 14 forks source link

Configure socks per container #24

Open ruihildt opened 2 years ago

ruihildt commented 2 years ago

Privacy Companion is a great tool but it would be nice it one were able to set separate proxies for different container environments within Firefox. So far I've used an addon called "Container Proxy" for this.

Mozilla has integrated that to their Firefox VPN implementation.

Also see #23

ruihildt commented 2 years ago

It is possible to access the container API to do this.

MahdiNazemi commented 1 year ago

I can think of two nice-to-have features for configuring a socks proxy per container:

  1. Always use the same proxy server for specific containers, e.g., Personal, Work, etc.
  2. Automatically connect to a proxy server from a list of user-selected proxy servers when a new temporary container is opened. Allowing the user to select all proxy servers that match his/her time zone (as set in the browser) would be great.
MahdiNazemi commented 1 year ago

I did some research and found that Firefox has a proxy.onRequest API , which is

Fired when a web request is about to be made, to give the extension an opportunity to proxy it.

browser.proxy.onRequest.addListener(listener, filter, extraInfoSpec) passes a proxy.RequestDetails argument to the listener, which includes the cookieStoreId. The listener can return a proxy.ProxyInfo object to guide the browser on which proxy to use.

One can keep a mapping of containers to proxies and return the proper proxy for each invocation of onRequest, e.g., predetermined proxies for Multi-Account Containers and randomly selected proxies within a country or city for Temporary Containers.

I tried to add this feature to the code, although I am unfamiliar with Vue.js; I am unsure where to place the logic related to container proxies. In particular, I thought Location.vue may be a good place to start, but the listeners I add there are never invoked, e.g., when I add a callback function on contextualIdentities.onCreated, the callback function is never called; moving the same piece of code to background/main.ts works as expected.

@ruihildt, can you please give me a few pointers on how I should proceed?

ruihildt commented 1 year ago

Containers aren't supported in Mullvad Browser.

We need to figure out a way forward first.

ruihildt commented 8 months ago

Now that the extension is proxying by intercepting request using the proxy API, it should be much easier to integrate with containers.

Any implementation would first need figuring out the relative priorities of proxying:

Are there still users interested in having some kind of proxy/containers integration?

UtilFunction commented 8 months ago

Are there still users interested in having some kind of proxy/containers integration?

Yes, definitely!

MahdiNazemi commented 8 months ago

I developed an add-on about a year ago where I could fix the proxy server for Firefox Multi-Account Containers and pick a random proxy server for Temporary Containers. The add-on did not have a UI and some parameters, like the city and list of providers, where hard-coded. But I can share parts of the code that dealt with proxying requests if you are interested.

ruihildt commented 8 months ago

Sure, I'd be happy to have a look when I get the time and opportunity.

MahdiNazemi commented 8 months ago

Here are the relevant parts of my code. As I mentioned earlier, there are lots of hard-coded parameters that should be cleaned up in the code or moved to the UI to be selected by the user.

browser.proxy.settings.set({value: {proxyType: "system", proxyDNS: true}});

// Define a request object to fetch Mullvad's proxy servers
// In my code, it reads from https://api.mullvad.net/network/v1-beta1/socks-proxies
// I fetch the list of servers on browser startup, but there may be better ways to do it.
request.addEventListener("load", () => {
  // Firefox leaks container's DNS if an invalid proxy is not defined.
  // The solution is to set an invalid manual SOCKS proxy under Network Settings,
  // e.g., localhost:1
  browser.proxy.settings.set({value: {proxyType: "manual", socks:"127.0.0.1:1", proxyDNS: true}});

  // Parse the JSON including the list of servers, filter the ones that are online, and return an object that includes per-country and per-city list of servers.

  // This is where I hard-coded the proxy servers for Multi-Account Containers.
  // This mapping should be done through the UI and stored on disk for future use.
  // Ideally, the user can select a country, city, or specific server for each persistent container. If they choose a country (city), we randomly select from all proxy servers in that country (city).
  const containerProxyMap = {};
  browser.contextualIdentities.query({}).then((containers) => {
    for (const container of containers) {
      if (container.name === "Personal") {
        containerProxyMap[container.cookieStoreId] = "IPv4 Address of the Desired Server";
      }
      else if (container.name === "Work") {
        containerProxyMap[container.cookieStoreId] = "IPv4 Address of the Desired Server";
      }
    }
  });

  const country = "My Country";
  const city = "My City";  
  const countryCityProxies = proxies[country][city];
  const localhosts = new Set(['localhost', '127.0.0.1', '[::1]']);
  browser.proxy.onRequest.addListener((requestDetails) => {
    try {
      const documentUrl = new URL(requestDetails.url);
      // I do not proxy requests made to localhost. This should probably be an option.
      if (localhosts.has(documentUrl.hostname)) {
        return {type: "direct"};
      }
      // I proxy requests made from the default container through the server I am currently connected to.
      if (requestDetails.cookieStoreId === "firefox-default") {
        return {type: "socks", host: "10.64.0.1", port: 1080, proxyDNS: true};
      }
      // If there is no pre-defined mapping for this container, e.g., it is a temporary container, select a random server. In my case, it selects from the country and city I have hard-coded, but this should also be selected by the user in the UI.
      if (!(requestDetails.cookieStoreId in containerProxyMap)) {
        const index = Math.floor(Math.random() * countryCityProxies.length);
        // Use the same proxy server for future requests made from this container. Ideally, we should remove the entry for the container when it has no more active tabs.
        containerProxyMap[requestDetails.cookieStoreId] = countryCityProxies[index].ipv4_address;
      }
      const hostname = containerProxyMap[requestDetails.cookieStoreId];
      return [{type: "socks", host: hostname, port: 1080, proxyDNS: true, failoverTimeout: 1}, {type: "socks", host: "10.64.0.1", port: 1080, proxyDNS: true}];
    }
    catch (e) {
      console.error(e);
      return {type: "failed"};
    }
  }, {urls: ["<all_urls>"]});
});
MahdiNazemi commented 3 months ago

The DNS leak issue explained near the top of the above snippet may be fixed in 128.0:

Firefox now proxies DNS by default when using SOCKS v5, avoiding leaking DNS queries to the network when using SOCKS v5 proxies.

MahdiNazemi commented 2 months ago

@ruihildt, is per-container proxy settings still being considered to be added to the Mullvad browser extension? The number of blocked requests has increased significantly in the past few weeks (Google and Reddit), and I am hoping using SOCKS5 proxy can eliminate or reduce the blocked requests.

ruihildt commented 2 months ago

@MahdiNazemi It's not a high priority, but I think you can already configure a website to load using a specific socks5 proxy right now.