Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐉🔥💀 (draft) (WIP) wireguard: allow whole-internet VPN configuration (do not merge) 💀🔥🐉 #66300

Closed
wants to merge 2 commits into from

Conversation

grahamc
Copy link
Member

@grahamc grahamc commented Aug 7, 2019

Mimick wg-quick's fwmark business.

wg-quick enables loose reverse path checking, so we do the same here.

To setup a whole-internet VPN with Wireguard after this commit, on
your "client" peer, add these settings:

networking.nameservers = [ "4.2.2.2" "4.2.2.3" ];
networking.wireguard.wg0.fwmark = 51820;
networking.wireguard.wg0.table = "51820";

and still on your client's configuration, set your "server" peer's
entry to:

networking.wireguard.wg0.peers = [
  ...
  {
    ...
    allowedIPs = [ "0.0.0.0/0" ];
  }
];

On your server, you need:

boot.kernel.sysctl."net.ipv4.ip_forward" = "1";

and to run (not sure how to best represent it in NixOS config):

sudo iptables -t nat -A POSTROUTING -j MASQUERADE
Motivation for this change
Things done
  • Tested using sandboxing (nix.useSandbox on NixOS, or option sandbox in nix.conf on non-NixOS)
  • Built on platform(s)
    • NixOS
    • macOS
    • other Linux distributions
  • Tested via one or more NixOS test(s) if existing and applicable for the change (look inside nixos/tests)
  • Tested compilation of all pkgs that depend on this change using nix-shell -p nix-review --run "nix-review wip"
  • Tested execution of all binary files (usually in ./result/bin/)
  • Determined the impact on package closure size (by running nix path-info -S before and after)
  • Ensured that relevant documentation is up to date
  • Fits CONTRIBUTING.md.
Notify maintainers

cc @

${optionalString (values.fwmark != null) ''
wg set ${name} fwmark "${toString values.fwmark}"
ip rule add not fwmark "${toString values.fwmark}" table "${toString values.table}"
ip rule add table "${values.table}" suppress_prefixlength 0
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't stress enough how I have no idea what I'm doing.

Copy link
Contributor

@zx2c4 zx2c4 Aug 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fancy table stuff should be conditionalized on /0 in allowed-ips, not on fwmark being set, since there are many uses of fwmark. If you're going to go this route, just copy the exact logic wg-quick(8) uses for these options.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ip rule add table "${values.table}" suppress_prefixlength 0
ip rule add table main suppress_prefixlength 0

That line should be main - we want to suppress the default route in the main routing table.

This seems to also be missing IPv6 support.

I agree with @zx2c4 this shouldn't be conditionalized on fwmark being set, but we probably shouldn't conditionalize on "allowed-ips contain a default route" either.

Users might set allowedIPsAsRoutes to false, configure peers with ::/0, 0.0.0.0/0, and add routes manually, or have a routing daemon doing it - in that case, we don't really have control about whether a default route is inserted.

Still, we should provide an option to enable the "fancy table stuff" and configure the routing table name and fwmark number being used.

@andir
Copy link
Member

andir commented Aug 7, 2019

You probably want to use networking.nat for the masquerade stuff: https://github.com/NixOS/nixpkgs/blob/release-19.03/nixos/modules/services/networking/nat.nix#L38

Copy link
Member

@ajs124 ajs124 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are multiple ways to achieve 0.0.0.0/0 VPNs besides fwmark, e.g. setting a separate route to your endpoint or using network namespaces, why did you decide on this one?

All of this looks mostly fine to me, but I would be interested in your reasoning.

nixos/modules/services/networking/wireguard.nix Outdated Show resolved Hide resolved
@@ -391,6 +414,7 @@ in
message = "networking.wireguard.interfaces.${interfaceName} peer «${peer.publicKey}» has both presharedKey and presharedKeyFile set, but only one can be used.";
}) all_peers;

networking.firewall.checkReversePath = "loose";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would every wireguard user want this or is this only relevant to people trying to do a 0.0.0.0/0 setup?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good question.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverse path filtering ensures some routing symmetry, meaning you can only receive packets from a src addr on an interface, when you would also use that interface to send to that address.

  • This is not strictly necessary for every wireguard setup.
  • It is probably necessary in this case, because you have two default routes in place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loose reverse path filters are the default for most systemd-based distros. I guess we can drop that line, and instead let #66482 handle this.

@grahamc
Copy link
Member Author

grahamc commented Aug 7, 2019

cc @zx2c4

Mimick wg-quick's fwmark business.

wg-quick enables loose reverse path checking, so we do the same here.

To setup a whole-internet VPN with Wireguard after this commit, on
your "client" peer, add these settings:

    networking.nameservers = [ "4.2.2.2" "4.2.2.3" ];
    networking.wireguard.interfaces.wg0.fwmark = 51820;
    networking.wireguard.interfaces.wg0.table = "51820";

and still on your client's configuration, set your "server" peer's
entry to:

    networking.wireguard.interfaces.wg0.peers = [
      ...
      {
        ...
        allowedIPs = [ "0.0.0.0/0" ];
      }
    ];

On your server, you need:

    boot.kernel.sysctl."net.ipv4.ip_forward" = "1";

and to run (not sure how to best represent it in NixOS config):

    sudo iptables -t nat -A POSTROUTING -j MASQUERADE
@grahamc
Copy link
Member Author

grahamc commented Aug 7, 2019

I chose this route because it was about 24 lines of diff to add it to the existing NixOS config, and it is what wg-quick does, which is good since I don't know anything about networking. ;)

example = 51820;
type = types.nullOr types.int;
description = ''
Mark packets with this value in fwmark.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which packets does it mark? Overlay or underlay?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know.

@lheckemann
Copy link
Member

#52411 related (issue for network-namespace-based implementation, including an implementation by @anderspapitto)

Copy link
Member

@WilliButz WilliButz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from what has already been commented, I don't really have anything to add 🙂

For myself I put toghether a 'works-for-me' setup to route everything through my vpn, I only have static default routes through my wireguard tunnel and use a stripped down dhcpcd which isn't allowed to add routes, dns servers or default gateways and instead only replaces the /32 and /128 routes to my wireguard endpoint with networking.dhcpcd.runHook.
This is certainly not for everyone, especially when it comes to weird hotel or train networks, so I appreciate the work on more viable built-in solutions like this and what @lheckemann mentioned 👍 .

@ivan
Copy link
Member

ivan commented Aug 8, 2019

As I mentioned on IRC, I cannot reach machines on my LAN with this setup enabled.

Copy link
Contributor

@zx2c4 zx2c4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fancy table stuff should be conditionalized on /0 in allowed-ips, not on fwmark being set, since there are many uses of fwmark. If you're going to go this route, just copy the exact logic wg-quick(8) uses for these options.

Do not merge this as-is.

Copy link
Member

@Ma27 Ma27 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change itself seems fine (apart from the comments already made). It's good that the solution is entirely opt-in as there are a lot of approaches to achieve something similar (with different up/downsides) and it still provides a simple solution for folks with less networking experience. 👍 👍

Additionally there are two minor, non-blocking things I'd like to bring up:

  • You explained how to use it pretty good in the commit message, however I think that it would be even better if this is mentioned in e.g. the NixOS manual to ensure that this information is easily accessible some years later :)

  • I didn't try it yet, but do you think it would make sense to write a simple testcase for this (in addition to the two wireguard tests we already have)?

${optionalString (values.fwmark != null) ''
wg set ${name} fwmark "${toString values.fwmark}"
ip rule add not fwmark "${toString values.fwmark}" table "${toString values.table}"
ip rule add table "${values.table}" suppress_prefixlength 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ip rule add table "${values.table}" suppress_prefixlength 0
ip rule add table main suppress_prefixlength 0

That line should be main - we want to suppress the default route in the main routing table.

This seems to also be missing IPv6 support.

I agree with @zx2c4 this shouldn't be conditionalized on fwmark being set, but we probably shouldn't conditionalize on "allowed-ips contain a default route" either.

Users might set allowedIPsAsRoutes to false, configure peers with ::/0, 0.0.0.0/0, and add routes manually, or have a routing daemon doing it - in that case, we don't really have control about whether a default route is inserted.

Still, we should provide an option to enable the "fancy table stuff" and configure the routing table name and fwmark number being used.

@zx2c4
Copy link
Contributor

zx2c4 commented Aug 11, 2019

wg-quick has dealt with lots of ugly corner cases over a while of development. Instead of repeating that process, why not just try to copy the basic logic and flow of it as much as possible, and then we can review it from there?

@Ma27
Copy link
Member

Ma27 commented Oct 18, 2019

@grahamc although I'm not 100% convinced whether this is the right approach (well, I really don't consider myself a network expert tbh), may I ask if there's anything missing to get this merged? In the end I think that a lot more users would start trying/using WireGuard if stuff like whole-internet-support tasks can be automated in the WireGuard module :)

@grahamc
Copy link
Member Author

grahamc commented Oct 20, 2019

I don't know :) I would rather another NixOS networking-friendly contributor merge it. Maybe @fpletz or @andir or @flokli or @aszlig.

Copy link
Member

@fpletz fpletz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR and the functionality it implements is not ready and should not be merged in the current state.

  • IPv6 is not implemented (needs the same rules with ip -6 rule)
  • As @zx2c4 and @flokli mentioned, policy routing with ip rule should not be automatically used if only fwmark is set.
  • If policy routing is used either with another new option or by matching default routes in allowedIPs (see previous point), please make it possible to configure the pref of the rules so that other policy routing rules can still be added and all can work together without issues.
  • Both added policy routing rules IMHO do not make any sense because they unconditionally (either with or without fwmark) route packets via the routing table for traffic inside the VPN. AFAIK fwmark is only applied to outgoing packets that are not inside the VPN (Wireguards UDP packets), so the correct rule should be something like ip rule add fwmark ${values.fwmark} pref 42 table main for traffic to the wireguard vpn endpoint and all other traffic should go to the wg routing table, like ip rule add pref 43 table ${values.table}, shouldn't it? Those rules can, depending on preference, also be negated but the target routing tables need to be exchanged.

@fpletz
Copy link
Member

fpletz commented Oct 21, 2019

There are multiple ways to achieve 0.0.0.0/0 VPNs besides fwmark, e.g. setting a separate route to your endpoint or using network namespaces, why did you decide on this one?

The problem is that separate routes only reliably work for static default gateway configurations. If you use DHCP, the DHCP client has to set the explicit route to your endpoint via the acquired default gateway which is not an option found in most DHCP clients. While dhcpcd can do this if you add a shell script hook that adds and removes the routes, it's not an ideal solution in general. (See @WilliButz's comment)

Using network namespaces adds another layer of complexity because patching of systemd units is needed. @orivej's solution in #52411 (comment) is nice to understand what's needed but it is not a general solution because IMHO for this feature the default network namespace should be the one that only routes via the VPN. Also note that our networking module is not aware of multiple network namespaces so the abstractions for network interface configurations cannot be used for interfaces on other netns than the default. This would need a lot of work to get right.

@grahamc
Copy link
Member Author

grahamc commented Oct 21, 2019

Just for reference, my goal for this PR was to demonstrate a PoC solution of mechanically what could be done to get it working. I never actually expected it to merge. (I think/hope I made that clear in all my comments where I'm not ever assertive about anything.)

@andir
Copy link
Member

andir commented Oct 21, 2019

I also do not feel very good regarding merging this.

There are the obvious reasons stated above. Additionally I am not feeling well with putting more scripted networking into NixOS. We are already at a point where trying to keep things going while moving the system forward becomes painful (think e.g. the default route issue with scripted networking vs. systemd-networkd, networking in initramfs, …).

We need a coordinated long-term effort to fix things up first.

I also wouldn't merge this as is because we have no way to verify it without running it on a machine and testing it manually. We are trying to sell some kind of "route everything through VPN by just flipping this switch" feature but aren't setup to verify that it will continue working. Ontop of that the wireguard maintainer clearly stated that we shouldn't be merging this without having a closer look at how and what wg-quick implements.

Following up with that @grahamc wrote just above we should probably downgrade this to a draft.

@grahamc grahamc changed the title wireguard: allow whole-internet VPN configuration 🐉🔥💀 (draft) (WIP) wireguard: allow whole-internet VPN configuration (do not merge) 💀🔥🐉 Oct 21, 2019
@tpanum
Copy link
Contributor

tpanum commented Mar 10, 2020

Is there anybody actively working on implementing this? Would be extremely useful to have. The current alternatives are fairly complex.

@flokli
Copy link
Contributor

flokli commented Mar 10, 2020 via email

@flokli flokli marked this pull request as draft June 6, 2020 19:06
@grahamc grahamc closed this Jul 1, 2020
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/route-all-traffic-through-wireguard-interface/1480/17

@bqv
Copy link
Contributor

bqv commented Jan 27, 2021

:(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet