5

I have a multi-user setup (Ubuntu) (simultaneously logged in users on different TTYs).

When I connect to NordVPN from one account using nordvpn connect, all users are now connected to the internet via that VPN.

How to somehow separate the networks of the users, meaning that when I connect to the VPN, only the current user is affected, and all connections of that user should use the VPN ?


  • nordvpn is just a wrapper around openvpn and it's possible to directly connect using openvpn, thus a pure openvpn solution would be helpful, too.
  • The users have root access via sudo.
  • I'm fine with a script solution.
  • Check on server config this option duplicate-cn. If –duplicate-cn exist and active comment it and restart OpenVPN server. – mature Apr 30 at 7:55
  • Create a virtual host and enter it; open your VPN from in there.? Alternately, depending on your packet filtering solution you may be able to redirect any traffic from the allowed user though the VPN using policy routing (and, conversely, any traffic bu other users away from it). – Grump Apr 30 at 7:55
  • @mature. I'm on the client side and have no influence on the server, the system users have no relation to the vpn users. – pLumo Apr 30 at 8:02
  • More lightweight than a virtual host: Create a network namespace, start nordvpn in this namespace; then start all processes that should use this VPN in this namespaces (which may be all processes of one particular user, or of several users; or uses may choose to start some processes there, and use "normal" internet for other processes). You need root rights to create the namespace. Also works with several different VPN connections. – dirkt Apr 30 at 10:56
1

Partial answer (needs more details in question):

One way to differentiate between "normal" internet connections and NordVPN internet connections is to create a network namespace, start nordvpn in this namespace, and then start all processes that should use this VPN in this namespace.

Details depend on how you want to use it:

  • If you have a single user that will always start NordVPN, you can create that network namespace on login, and also start all processes for that user in that network namespace. As a consequence, that user will never have "normal" internet connections.

  • If you have multiple users who both want to user "normal" internet connections and NordVPN connections, you can write a script that creates that namespaces, starts NordVPN in it, and gives the user a terminal where he/she can start additional applications, or maybe even already start applications like a web browser in this namespace. This script will then replace the connect command.

There are probably many more ways to do it, depending on what your requirements are. So please edit the question and describe your requirements/circumstances.

You need root rights to create the namespace. That means you will either need the script to be setuid-root (which may be a security issue if the script has bugs), you may need to give users sudo access, etc.

  • Thanks for your answer. I'm not sure which information you need. Isn't the first solution also possible after login similar to second solution with a script, but then used for all connections/applications from that user? – pLumo May 2 at 9:08
  • I need to understand in detail how the use case looks like (I've very roughly described to different use cases in the bullet points). So: Is it always the same user who will start NordVPN? Are there multiple different users who will start NordVPN? Will those users need normal internet connections as well? Etc. The more info you can provide, the easier it is to catch potential problems. – dirkt May 2 at 9:26
  • The point to remember is that when using a network namespace, only processes that use this network namespace will use NordVPN. So if a user has processes outside this NS and these process spawn subprocesses, they'll also be outside the NS. If you want to give each user the flexibility to start and stop NordVPN at any time, it might be easier to just give each user his/her own network namespace right on login, and then each user can change all network settings (including starting/stopping NordVPN) without affecting any other user. – dirkt May 2 at 9:30
  • 1
    that last one would be exactly what I want: just give each user his/her own network namespace right on login – pLumo May 2 at 9:31
  • Then see e.g. here. As it says, "in theory you should be able to use PAM to automate this", but I haven't done this myself, and it may need some fiddling to get right. For custom PAM scripts, see e.g. here. You'll still need to make sure the "first" user script (e.g. the one executed by the display manager) gets moved to the new namespace. – dirkt May 2 at 9:37
1

How to use vpn separately for different user:

Situation where users are using different user space:

By default user space separate the network (netns) and thus nordvpn connect will not affect other user space but this is not the default functioning of users system under linux, you will need to setup a different user space in order to separate the network of each user; also the a network interface can only exist on a single name space bridges or veth interface and then used to tunnel traffic between user spaces.

Situation where users are using the same user space:

Linux user system remain under the same network system if one user is connected to the wifi the other user will benefit from that connection because the network card is setup at the root level and thus shared for every one who are using the network with its default setup.

A VPN connection is done with a new virtual interface (tun or tap) and is linked to the main network interface (wifi or eth0)... when the vpn connection is initialized a tun/tap interface is created then connected to the VPN server and create a tunnel but this does not mean that all the connection are tunneled over the VPN interface, in order to have a classic working VPN connection first the connection is initialized with the virtual interface then a route is added to force all the connection to go through the VPN interface this is called routing.

Knowing those informations the solution would be to initiate the VPN connection without routing and then setup the routing separately for each user. no change are required for users that does not require the VPN; A special routing needs to be added with iptables/ip-route for users that needs to use the vpn. In other words the VPN interface will be setup but it wont be the default interface (because default vpn routing rules wont be pushed)

VPN Default Situation: [Connect Command] > [Create-Tun/Tap] > [Connect Tun/Tap] > [Route To Make Tun/Tap As Default Interface]

VPN Without Route: [Connect Command] > [Create-Tun/Tap] > [Connect Tun/Tap]

VPN With Custom Route: [Connect Command] > [Create-Tun/Tap] > [Connect Tun/Tap] then manually or automatically add [Custom routes]

Connect to your vpn without using the "route" step then push/setup a custom route, this could be done with iptables/ip-route or with the VPN conf setup file.

How can to configure openvpn without pushing default gateway/route:

Edit your vpn conf file and add route-nopull directive. (if nordvpn command are used accessible openvpn conf file, you can edit them according your need otherwise you would need to use openvpn or network manager to connect to your vpn)

Use specific interface for a particular linux user:

This guide is achieving exactly the same thing you want to achieve. otherwise those answers give a detailed alternative.

1
+100

I think this problem may be addressed in a number of ways, depending on the overall pre-existing setup, possibility to alter the provider’s own configuration, actual vpn types to support, and willing to undergo more or less complex setups.

I would also say that for solutions specific to the NordVPN service you might also try and ask them for support, because the nordvpn command may not necessarily use OpenVPN, it might rather use IPSec, or even PPTP/L2TP.

However, for a generic OpenVPN solution, which you say is fully compatible with your NordVPN service, I think the best approach might be using policy routing, possibly with a few variants.

But first, seen your interest in namespaces, I'd like to mention my opinion about a possible approach using those:

Certainly by using namespaces one would obtain true isolation of networking between users, with the main and notable advantage of being a generic solution for any type of vpn, not just OpenVPN.

But the required tools to set things up are not commonly installed by many Linux distribution, as the problem in question doesn't seem one that can be addressed simply by using the unshare or ip netns exec commands, because those allow visibility of the new namespace only to those processes run from then on and under that session of commands, not to the user's entire pre-existing session.

Also, even by installing the required tools to set things up the way they should, the system would require careful setup in order to separate each user into its own network namespace while making use of the same physical net device as well as sharing the unix-domain sockets and services running for the localhost. Setting up bridges (or macvlans), subnets, assigning addresses, maybe NATting, and other complexities. It is probably doable, but likely not simple to make it straight and I think much more complex than the original problem.


So, back to a routing policy approach, as far as I can conceive, the base solution, probably the simplest workable one, likely sufficient for most common use-cases, or as a starting point before venturing into more sophisticated variants, would use separate routing tables per each user’s tunnel to be looked up via routing policy based on user’s UID.

It would have the following main characteristics:

  • require a (fairly) standard version of OpenVPN (i.e. not heavily customized by provider)
  • require an iptables companion rule to mark the relevant traffic based on user’s UID number
  • careful integration may be required if used along pre-existing firewall or routing rules
  • cannot however identify processes run by the user via sudo (because these run with UID 0) nor setuid commands or anyway applications run as root. These are mostly not the case for the typical desktop scenario, but may well happen and would not be handled as intended (i.e. that part of traffic would not go into the vpn)

Everything can be handled by two helper scripts. One to be used by OpenVPN, the other to be used to run openvpn itself.

Standard OpenVPN uses the ip command to set things up on the tun/tap device and to install routes pushed by the VPN provider. The problem is that by default the ip command operates on the main routing tables, which are used by the whole system thus including all its users.

However, OpenVPN allows to specify a different command to be used for the settings it needs to apply, and so we can provide a wrapper over ip that simply operates on different routing tables.

Such (tentatively named myip.sh) wrapper script might be like:

#!/bin/bash -

my_session="$(ps -p $$ -o sid --no-header)" || exit 1
real_uid="$(ps -p $$ -o ouid --no-header || ps -p $my_session -o ruid --no-header || echo 0)"

real_ip_cmd=/sbin/ip

if [ $real_uid -ne 0 ] && [[ "$1" = ro* ]] ; then
    ! [ $2 ] && exec $real_ip_cmd "$1" list table $real_uid
    [[ "$2" =~ ^([adcfls]|rep).*$ ]] && exec $real_ip_cmd "$1" "$2" table $real_uid "${@:3}"
fi
exec $real_ip_cmd "$@"

The first two lines serve to try and retrieve the real UID that’s running OpenVPN. We use that UID as table id. This way each user would have its own routing table when he/she starts a vpn.

The if-then-fi block catches invocations for route settings (mimicking ip command’s ability to accept shortened yet unambiguous keywords), and simply prepends all such commands passed by openvpn with a table option carrying an id equal to the real UID of the user. All other ip commands (i.e. non route) pass through untouched.

The other script wraps the openvpn command, while setting things up to make traffic generated by the user be routed according to its distinct table. The gist of this wrapper script could be included into the provider’s own configuration file, allowing for better seamless integration. This would help in case one would like to establish the vpn non-interactively (e.g. at boot time). However, by using this wrapper script instead, one does not require fiddling with the configuration file, and this is why I've chosen to present this version.

So, such single wrapper script might be like this:

#!/bin/bash

my_session="$(ps -p $$ -o sid --no-header)" || exit 1
real_uid="$(ps -p $$ -o ouid --no-header || ps -p $my_session -o ruid --no-header || echo 0)"
[ $real_uid -eq 0 ] && { echo "will not run for UID 0" >&2 ; exit 1; }

remove_tagging() {
        iptables -t mangle -D OUTPUT -m owner --uid $real_uid -j MARK --set-mark $real_uid
        ip rule del fwmark $real_uid
        ip route flush table $real_uid
} 2>/dev/null

trap 'remove_tagging' EXIT

source <(ip route | sed "s/^/ip route add table ${real_uid} /")

(
ip -o monitor | grep -qm 1 '^[0-9]\+: tun[0-9]\+[[:blank:]]\+inet '
ip rule add fwmark $real_uid lookup $real_uid
iptables -t mangle -A OUTPUT -m owner --uid $real_uid -j MARK --set-mark $real_uid
) &

openvpn --iproute myip.sh --config tunnel-config.ovpn

The first three lines again serve to try and retrieve the real UID of the user, and will not proceed if that is not obtainable.

Then we have a function to be run on EXIT from the script. The function removes the settings put in place by the following sub-shell.

But first we copy the whole current main routing table into the distinct table for the user.

Then we run the sub-shell that waits on a tunX device to be set up by OpenVPN and then adds the routing rules and the iptables companion rule that marks the traffic generated by the user from then on. We run this in the background because we need to run OpenVPN in the foreground in case it needs to ask for username and password.

The source command, the sub-shell (without the ip -o monitor pipeline) and the remove_tagging function, are the parts that could go in the vpn configuration file as (respectively) up, route-up and route-pre-down options. Doing so would allow to get rid of this wrapper script entirely.

Finally we run the actual openvpn command, telling it to use our own myip.sh script as command to set up networking.


A possible slight variant might be by using more recent versions of iproute2 package (which provides the ip command), those having the uidrange option to be used instead of fwmark in routing rules.

Such variant would thus eliminate the iptables commands, and replace the ip rule fwmark .. command with a ip rule uidrange ${real_uid}-${real_uid} lookup $real_uid command (the same goes for its respective ip rule del .. command).


Another variant for some support of commands run via sudo would be to make policy routing based on user’s group (GID) instead of UID. This would have additional basic requirements:

  • a unique GID to be existent for the user, as commonly is on many Linux distributions (including Ubuntu)
  • sudo run with extra options (-g <user> -u root or equivalent configuration in sudoers) to retain original GID number

However, it would still not be able to identify UID 0 processes run by the user outside of such sudo.


To really address the “UID 0 processes” problem, one might use cgroups, but naturally this has yet additional requirements:

  • iptables companion rule to mark the relevant traffic with iptables’s own specific "cgroup" module, which was not yet available on e.g. Ubuntu 14.04
  • may conflict with pre-existing cgroup setups already in place

Using cgroups, setting the net-class-id might be handled pretty well via a simple script to be run by pam_exec.so

#!/bin/bash -e

uid=$(id -ru "${PAM_USER}")
mkdir -p /sys/fs/cgroup/net_cls/user/${uid} && echo $uid > /sys/fs/cgroup/net_cls/user/${uid}/net_cls.classid
echo $$ > /sys/fs/cgroup/net_cls/user/${uid}/cgroup.procs

and then adding the following line to the desired config files under /etc/pam.d:

session optional    pam_exec.so /root/set-net-cls-id.sh

The above line might be possibly put in the traditional /etc/pam.d/common-session file to cover all sessions originated via any interactive service like ssh, console logins, desktop environments, etc.

With this setup, the iptables command to be used in the wrapper script would be:

iptables -t mangle -A OUTPUT -m cgroup --cgroup $real_uid -j MARK --set-mark $real_uid

and its respective deletion command with -D in place of -A.

A possible variant of the cgroup solution might be its integration into systemd, so that one could use systemd's ability to handle cgroups directly without using pam_exec.

Note however that this cgroup setup cannot work well with Ubuntu 14.04 because that version used to set cgroups for each user's session and would thus overwrite the setup put in place by this solution. You would rather need to set the classid value of the cgroup created on-fly for each session, whose name can be easily obtained by parsing cat /proc/self/cgroup output.

  • Thanks for your answer, very detailed and good information. I'll try that. – pLumo May 7 at 8:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.