MichaIng / DietPi

Lightweight justice for your single-board computer!
https://dietpi.com/
GNU General Public License v2.0
4.76k stars 495 forks source link

DietPi-VPN | Add split tunnel feature #2758

Open VasilevAndrei opened 5 years ago

VasilevAndrei commented 5 years ago

Creating a bug report/issue

Required Information

Additional Information (if applicable)

Steps to reproduce

  1. Install and setup NordVPN
  2. Try to connect to ssh or ngnix server via internet

Expected behaviour

Working vpn tunnel and the ability to access the nginx server via ip from the Internet.

Actual behaviour

VPN tunnel works. Can't connect to ngnix.

Extra details

On my dietPi machine, there is an nginx server working as a reverse proxy for services like sonarr, lidar, radarr, deluge, etc. I also have port forwarding configured on the router. After installing NordVPN, the services is not accessible from outside the local network. SSH also does not work.

MichaIng commented 5 years ago

@VasilevAndrei Many thanks for your report.

That is somehow the expected behaviour when being connected to a VPN tunnel. All network packets are forced through the VPN tunnel, regardless if the initial request/connection was coming from another network interface.

I was already tinkering on a simple solution. Found marking connections, established from outside the VPN, via iptables a good solution:

GCI_PRESERVE=1 G_CONFIG_INJECT '42[[:blank:]]' '42 bypass_vpn' /etc/iproute2/rt_tables
ip r add default dev wlan0 via 192.168.178.1 table 42
iptables -t mangle -A PREROUTING -i wlan0 [-m conntrack --ctstate NEW] -j CONNMARK --set-mark 42
iptables -t mangle -A OUTPUT -m connmark --mark 42 -j MARK --set-mark 42
ip rule add fwmark 42 table 42
  1. Adds a new IP table. Number (42) and name (bypass_vpn) can be freely chosen. Number not 100% free since there are four reserved for the system tables. Can be seen by editing the file.
  2. Add/Set the default route for this IP table to your main network device and default gateway (192.168.178.1 is my router IP).
  3. Mark connections, established through the main network interface wlan0 (instead of through the VPN tun0/wg0 interface). The mark number can be again freely chosen. I used "42" here again for no special reason. -m conntrack --ctstate NEW is optional. It marks only "new" connections then. For me with or without it just worked the same.
  4. Mark any packet, if it belongs to a connection with the previously set mark. Again I used "42" here for no special reason 😄.
  5. Finally add an IP rule for outgoing packets with the set mark (42), to use the previously created IP table, so send them through the main network interface where the connection was established through instead to the VPN tunnel interface.
undershot1 commented 4 years ago

This script is extremelly useful. I've written a script which updates the nginx reverse file to my needs for if I do a fresh install, I'd also like to add these lines to it.

How would I add the GCI_ line to a batch script? The ip and iptables applications are ok, as I can just use the absolute path, but not sure how to do global functions and variables.

MichaIng commented 4 years ago

@undershot1 You would need to source DietPi-Globals for the G_CONFIG_INJECT function: . /DietPi/dietpi/func/dietpi-globals But, assuming that there is no IP table 42 on a fresh install, you can simply do instead:

echo '42 bypass_vpn' > /etc/iproute2/rt_tables

I am no batch expert, hence syntax might be different there 😉.

ezar commented 3 years ago

Can I add this script for up.sh? We need to remove using down.sh? Do you have sample? If I don't use wireless, Can I change wlan0 to eth0? Regards,

MichaIng commented 3 years ago

Yes that should work from the up.sh and makes sense to remove the rules in the down.sh, although they should not hurt as they enforce only what is default anyway without active VPN.

To remove the iptables rules (all rules in mangle table): iptables -t mangle -F To remove the routing rule: ip rule del fwmark 42 table 42

But use all of this with caution and test carefully, as I never did. Do from local console (instead of SSH/remote access) so that you cannot accidentally lock out yourself 😉.

MichaIng commented 3 years ago

Some ideas/references: https://www.htpcguides.com/force-torrent-traffic-vpn-split-tunnel-debian-8-ubuntu-16-04/ And the following simple solution that does not require iptables but works with ip rules only: https://dietpi.com/phpbb/viewtopic.php?t=8841

bbsixzz commented 3 years ago

Looking forward to this!

bbsixzz commented 2 years ago

I see this has been removed from planned for implementation, does it mean the idea is abandoned?

MichaIng commented 2 years ago

The issue is still open, means I'm open for an implementation. However, I do not have the time/priority to start with the soon. If anyone else does, I think it can be similarly implemented like the killswitch: https://github.com/MichaIng/DietPi/blob/05d9289/dietpi/dietpi-vpn#L399 Doing this for individual choosable ports is probably the simplest way, but based on UNIX user as an alternative. Bonus would be to allow inclusive as well as exclusive split tunnelling, means either everything is tunnelled, but the defined ports/users, or only the defined ports/users are tunnelled.

bbsixzz commented 2 years ago

I'm using policy-based routing now with Transmission thanks to sellibitze on reddit.

My /etc/wireguard/client.conf looks like this:

[Interface]
Address = $ipv4/32, $ipv6/128
PrivateKey = ...
Table = 51413
PostUp = ip -4 rule add from $ipv4 table 51413
PostUp = ip -6 rule add from $ipv6 table 51413
PostUp = iptables -I OUTPUT -s $ipv4 ! -o %i -j DROP
PostUp = ip6tables -I OUTPUT -s $ipv6 ! -o %i -j DROP
PostDown = ip -4 rule del from $ipv4 table 51413
PostDown = ip -6 rule del from $ipv6 table 51413
PostDown = iptables -D OUTPUT -s $ipv4 ! -o %i -j DROP
PostDown = ip6tables -D OUTPUT -s $ipv6 ! -o %i -j DROP

[Peer]
PublicKey = ...
Endpoint = ...
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepAlive = 25  # useful for port forwarding

Running curl icanhazip.com shows the WAN IP and torrent-ip-checker in Transmission shows the VPN IP.

This allows me to set up a secondary WireGuard interface and run a VPN server on the same machine.


The secondary VPN interface fails to load after boot with sudo systemctl enable wg-quick@server

It works fine if done manually with sudo systemctl start wg-quick@server

This can be fixed by editing /etc/systemd/system/multi-user.target.wants/wg-quick@server.service

Add ExecStartPre=/bin/sleep 10 above ExecStart=/usr/bin/wg-quick up %i

This will delay the interface coming up by 10 seconds.


I can access my server with WireGuard VPN while keeping Transmission connected to a separate WireGuard VPN.

MichaIng commented 2 years ago

Ah awesome, many thanks for sharing. So the WireGuard settings Table = 51413 applies WireGuard rules to table 51413 instead of default 51820, but more importantly it seems to give it a lower priority than the "main" table while by default they get a higher priority. So nothing is routed by default through the tunnel anymore. You now add rules to route WireGuard IP originating packets through the tunnel and bind Transmission to that IP, clever 🙂. Just to assure my guess with the priority, can you show:

ip rule
bbsixzz commented 2 years ago
0:  from all lookup local
32765:  from 10.0.125.252/24 lookup 51413
32766:  from all lookup main
32767:  from all lookup default

If the Transmission VPN goes down nothing leaks because nothing gets transmitted anywhere.

MichaIng commented 2 years ago

Ah I was wrong, not the routing tables have priorities but the rules themselves, of course. The new rule has the same priority as the default one (when not declaring Table or setting Table = auto in the WireGuard config), but it seems that WireGuard does not create any rule, but the table only with non-auto value. So you need/can then define own rules which do not apply to all (non-local) requests, hence the VPN becomes an opt-in feature.

If the Transmission VPN goes down nothing leaks because nothing gets transmitted anywhere.

Makes sense with that rules.

A bit sad is that all this is working so easily only with WireGuard, while DietPi-VPN uses OpenVPN, which force traffic through the tunnel not via rules but via routers: It creates two routes (added to the main table), each covering half of the full IP range, so that they override the default route but not more specific ones (LAN, local). We'd need to create additional rule(s) to either force traffic for selected applications to bypass the VPN or to bypass the VPN with everything, then force selected applications though it only. Binding an application to the VPN IP works with WireGuard, where the IP never changes, but not for OpenVPN, where it is random, so it needs to be done other ways, notably connection/packet marks based on user or port(s).