pia-foss / desktop

Private Internet Access - Desktop VPN Client for Windows/macOS/Linux
Other
262 stars 50 forks source link

Allow split-tunneling from command line #34

Open piramiday opened 2 years ago

piramiday commented 2 years ago

it would be useful for all headless installations to be able to control split-tunneling via the command-line piactl tool.

for the time being, it would be awesome to get some clunky way to accomplish the same result, e.g. hard modifying the preferences to simulate what the GUI would have done, etc.

the feature has been proposed here, as well: https://www.privateinternetaccess.com/helpdesk/community/view/split-tunnelling-through-command-line

JonathonH-PIA commented 2 years ago

Absolutely agree, this would be a great feature. Mainly just needs some new interface in piactl to add/remove apps, since the infrastructure to get and set preferences is already there.

We do have the clunky way now with the --unstable applysettings command, which lets you modify the settings JSON more-or-less directly. This uses the same mechanism the GUI uses to apply settings. (It's behind "--unstable" because the JSON structure could change, but historically it has been stable.)

The settings structure is documented in settings.h, for example here's the definition of a "split tunnel application rule": https://github.com/pia-foss/desktop/blob/508b67e66a89ed44401dde5849f00ae47473b4d5/common/src/settings.h#L956

# These work on all platforms, on Windows be aware that the shell quoting will differ

# Enable split tunnel
$ piactl -u applysettings '{"splitTunnelEnabled":true}'

# Set the split tunnel app rules.  This replaces any existing rules.
# Appending would need to dump the existing rules (piactl -u dump daemon-settings)
# and use something like 'jq' to manipulate the rules.
# 'path' is an executable path on Win/Linux or an app bundle path on macOS.
# 'mode' is 'exclude' for "bypass" or 'include' for "only VPN".
$ piactl -u applysettings '{"splitTunnelRules": [ {"path":"/usr/lib/firefox/firefox","mode":"exclude"}, {"path":"/usr/lib/chromium/chromium","mode":"include"} ]}'

# Set the split tunnel IP rules.  Again, replaces any existing rules.
# Use CIDR notation for the subnet.  Use /32 for an exact IPv4 IP only (or /128 for IPv6).
# 'mode' must always be exclude, no other modes currently exist for subnet rules.
$ piactl -u applysettings '{"bypassSubnets": [ {"mode":"exclude", "subnet":"172.16.0.0/16"} ] }'

# The "fixed rules" for Routed Packets, All Other Apps, and split tunnel Name Servers are all
# boolean settings.
$ piactl -u applysettings '{"defaultRoute":true}'   # All Other Apps = Use VPN
$ piactl -u applysettings '{"routedPacketsOnVPN":true}'  # Routed Packets = Use VPN
$ piactl -u applysettings '{"splitTunnelDNS":true}' # Name Servers = Follow App Rules

It'd be awesome to get a proper interface for split tunnel settings, but for now 'applysettings' can hopefully do what you need.

piramiday commented 2 years ago

great, thanks, I'll try it out. do I need to piactl connect after such applysettings lines?

JonathonH-PIA commented 2 years ago

Generally, yes - and a piactl connect when connected with no reconnect needed is just ignored anyway, so you can just fire it off and let the daemon decide.

Specifically, defaultRoute and splitTunnelDNS always require a reconnect; splitTunnelEnabled usually does depending on the other settings. App and IP rules don't require a reconnect (though you may need to restart the app, depending on the app). needsReconnect is set in daemon state whenever a reconnect is needed.

piramiday commented 2 years ago

great stuff, thanks! :+1:

I'm okay with the clunky command (for now) -- feel free to close this issue, or to leave it open as a placeholder for the feature request.

while we are at it, though, how come nobody from PIA patrols the "community" portal? all requests appear as "awaiting review"...

JonathonH-PIA commented 2 years ago

I'll leave it open - I think it's a great feature idea. I'm not sure where it will fall on our priorities list with everything we have in the pipeline, but if somebody wandered across this issue and wanted to put together a PR, that'd be amazing :star_struck:

That's a great question re: the community portal, I'll see what I can find out about that.

JonathonH-PIA commented 2 years ago

Thanks for checking on the community portal - this was recently handed over to our CS team, and they're going to review issues monthly to send out to each department. There are some good suggestions on there that I'd like to get on our roadmap!

piramiday commented 2 years ago

quick follow up question: let's say that I would like to directly intervene on the system config without reconnecting PIA. is it doable? what should I do, broadly?

if I add my fresh IP to the allowed list like so:

user@host:~$ sudo iptables -I piavpn.r.305.allowSubnets 1 -j ACCEPT -d 12.34.56.78/32

which seems to me to be the only modification that PIA applies, well, it does not work.

if instead I act directly on the OUTPUT chain and then add my IP to the routing table, then it works:

user@host:~$ sudo iptables -I OUTPUT 1 -j ACCEPT -d 12.34.56.78/32
user@host:~$ sudo route add -host 12.34.56.78/32 gw 10.0.0.1

what am I missing? thanks.

banister commented 2 years ago

Hi @piramiday - the way we manage routing for split tunnel subnets is a little different than tradtional routing rules. We mark packets heading towards split subnets with the "excludePacketTag", see here: https://github.com/pia-foss/desktop/blob/master/daemon/src/posix/posix_firewall_iptables.cpp#L976-L984

This mark is then used by our routing policies to route the traffic out the physical interface.

If you add rules of the form -d <your subnet> -j MARK --set-mark 12817 to the piavpn.r.90.tagSubnets chain in the mangle table, it should work as expected.

piramiday commented 2 years ago

I have just tried but unfortunately it does not seem to be working:

$ sudo iptables -I INPUT 1 -s 12.34.56.78/32 -j ACCEPT
$ sudo iptables -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  12.34.56.78          anywhere
[...]

$ sudo iptables -I piavpn.r.305.allowSubnets -d 12.34.56.78/32 -j ACCEPT
$ sudo iptables -L piavpn.r.305.allowSubnets
Chain piavpn.r.305.allowSubnets (1 references)
target     prot opt source               destination         
ACCEPT     all  --  anywhere             12.34.56.78

$ sudo iptables -t mangle -I piavpn.r.90.tagSubnets -d 12.34.56.78/32 -j MARK --set-mark 12817
$ sudo iptables -t mangle -L piavpn.r.90.tagSubnets
Chain piavpn.r.90.tagSubnets (1 references)
target     prot opt source               destination         
MARK       all  --  anywhere             12.34.56.78         MARK set 0x3211

any thoughts? thanks.

banister commented 2 years ago

Hm, I would suggest adding a rule via the app - confirm that works - then look at the content of these chains for that app-added subnet. Then reproduce that for your custom subnets.

After doing that, if that fails to work, i can look into it more deeply, but let's make sure we check that box first 😛

piramiday commented 2 years ago

with respect to my old post, the problem was that yet another rule in the mangle table was needed. minimal script:

IP=12.34.56.78
sudo iptables -t filter -A piavpn.r.305.allowSubnets  -d "$1/32" -j ACCEPT
sudo iptables -t mangle -A piavpn.r.200.tagFwdSubnets -d "$1/32" -j MARK --set-mark 12817
sudo iptables -t mangle -A piavpn.r.90.tagSubnets     -d "$1/32" -j MARK --set-mark 12817

which of course will be applied if splitTunnelEnabled is set to true.

still, it is perfectly reasonable to have piactl directly insert rules using iptables without having to reconnect whenever the user wants to add an excluded IP address. it would be awesome to have some kind of command-line capability for this, which was the original point of my issue.

on a related note, it would be useful to have a way to add an IP to an already-present list of excluded IPs, instead of specifying the entire JSON bypassSubnets list with the unstable applysettings command.

I will then leave this issue open as a reminder that such features would be quite useful.

ghost commented 8 months ago

+1 for the feature

val-olfr commented 6 months ago

Hi. Meet the same issue. After some debugging found next cli only workaround.

  1. Stop the vpn service:

    sudo systemctl stop piavpn
  2. Find where your pia ctl dir located (if you don't know yet):

    systemctl cat piavpn | grep 'ExecStart'
  3. Edit PIA VPN configuration file, add your required ip/subnet:

    sudo vi /opt/piavpn/etc/settings.json

I need to set bypass only for ip so I changed only these keys:

According to your need you can play with other sections. The simplest way - set required configuration in UI, close Settings and review configuration file.

I have not found example in Google so here is a full example of my configuration for your reference:

{
  "allowLAN": true,
  "automaticTransport": true,
  "automationEnabled": false,
  "automationRules": [],
  "betaUpdateChannel": "beta",
  "blockIPv6": true,
  "bypassSubnets": [
    {
      "mode": "exclude",
      "subnet": "121.78.53.64/29"
    }
  ],
  "cipher": "AES-128-GCM",
  "connectOnLaunch": false,
  "defaultRoute": true,
  "desktopNotifications": true,
  "enableMACE": false,
  "favoriteLocations": [],
  "includeGeoOnly": true,
  "killswitch": "auto",
  "largeLogFiles": false,
  "lastDismissedAppMessageId": 0,
  "lastUsedVersion": "3.3.1+06924",
  "localPort": 0,
  "location": "auto",
  "macStubDnsMethod": "NX",
  "manualServer": {
    "cn": "",
    "correspondingRegionId": "",
    "ip": "",
    "openvpnNcpSupport": false,
    "openvpnTcpPorts": [],
    "openvpnUdpPorts": [],
    "serviceGroups": []
  },
  "method": "openvpn",
  "mtu": -1,
  "offerBetaUpdates": false,
  "overrideDNS": "pia",
  "persistDaemon": true,
  "portForward": false,
  "primaryModules": [
    "region",
    "ip"
  ],
  "protocol": "udp",
  "proxyCustom": {
    "host": "",
    "password": "",
    "port": 0,
    "username": ""
  },
  "proxyEnabled": false,
  "proxyShadowsocksLocation": "auto",
  "proxyType": "shadowsocks",
  "ratingEnabled": true,
  "recentLocations": [],
  "remotePortTCP": 0,
  "remotePortUDP": 0,
  "routedPacketsOnVPN": true,
  "secondaryModules": [
    "quickconnect",
    "performance",
    "usage",
    "settings",
    "account"
  ],
  "serviceQualityAcceptanceVersion": "",
  "sessionCount": 12,
  "showAppMessages": true,
  "splitTunnelDNS": true,
  "splitTunnelEnabled": true,
  "splitTunnelRules": [],
  "successfulSessionCount": 9,
  "surveyRequestEnabled": true,
  "themeName": "dark",
  "updateChannel": "release",
  "windowsIpMethod": "dhcp",
  "wireguardPingTimeout": 60,
  "wireguardUseKernel": true
}
  1. Start VPN service again:

    sudo systemctl start piavpn
  2. Turn on VPN connection:

    piactl connect
  3. Review your iptables configuration to confirm that rules for your ip-s have been applied:

    
    iptables -S | grep allowSubnets

-N piavpn.305.allowSubnets -N piavpn.a.305.allowSubnets -N piavpn.r.305.allowSubnets -A piavpn.305.allowSubnets -j piavpn.r.305.allowSubnets -A piavpn.a.305.allowSubnets -j piavpn.305.allowSubnets -A piavpn.anchors -j piavpn.a.305.allowSubnets -A piavpn.r.305.allowSubnets -d 121.78.53.64/29 -j ACCEPT

piramiday commented 6 months ago

hey @val-olfr, your comment is thorough but unnecessary. :rabbit:

as a brief recap, let me explain that the problem is twofold.

how to modify settings from the command line? clunky but solved! you can specify your custom "bypass subnet" parameter with the applysettings command:

$ piactl -u applysettings '{"bypassSubnets": [ {"mode":"exclude", "subnet":"172.16.0.0/16"} ] }'

(as quoted from https://github.com/pia-foss/desktop/issues/34#issuecomment-916072320, which works well.) of course you need to have the other parameters consistently set, eg splitTunnelEnabled set to true, etc.

most importantly, you need to reconnect so that PIA might add the new entries to iptables.

how to accomplish the same without reconnecting? clunky but solved! that is the point of my previous post: https://github.com/pia-foss/desktop/issues/34#issuecomment-1079964700 just a few lines of code and no reconnect necessary.

your comment forgets about the applysettings command and skips over my three-liner to solve the iptables problem without reconnecting. :rocket: