aboutsummaryrefslogtreecommitdiffgithub log msg author committer range
blob: e09a817241becd7a5739eb108630ca1372f7b445 (plain)
 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235  +++ date = "2015-08-09T22:55:31-04:00" draft = false title = "Running Select Applications through OpenVPN" description = "How I configured my GNU/Linux system to run only certain applications through a VPN" +++ ## This is outdated now. Please see the [README of the nsdo GitHub repository][11] instead --- --- --- *Note: I base this method heavily on [a great article by Sebastian Thorarensen][1].* To isolate VPN applications from applications running 'bare,' I use the following method, which involves [nsdo][10]. It has the handy effect of putting each instance of the openvpn client in its own network namespace, allowing you to run a bunch of VPNs at the same time, each available only to programs you choose. First, I leverage the [Arch openvpn package's openvpn@.service][2] systemd [service][3] (based on [upstream's slightly different version][7]), which I'll paste here for posterity: [Unit] Description=OpenVPN connection to %i [Service] PrivateTmp=true Type=forking ExecStart=/usr/bin/openvpn --cd /etc/openvpn --config /etc/openvpn/%i.conf --daemon openvpn@%i --writepid /run/openvpn@%i.pid --status-version 2 PIDFile=/run/openvpn@%i.pid CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_READ_SEARCH LimitNPROC=10 DeviceAllow=/dev/null rw DeviceAllow=/dev/net/tun rw [Install] WantedBy=multi-user.target (For instance, you'd start an openvpn instance configured in /etc/openvpn/foo.conf with systemctl start openvpn@foo.) However, the unit needs a few modifications. First, calling setns() to change network namespaces requires [CAP_SYS_ADMIN][8], a capability the vanilla unit does not provide. Second, to make OpenVPN keep the same network namespace across VPN reconnections or daemon restarts (e.g., after a suspend), a separate unit must set up the destination network namespace. To solve both issues, I put the following in /etc/systemd/system/openvpn@.service.d/netns.conf, a [systemd drop-in unit][9]: [Unit] Requires=netns@%i.service After=netns@%i.service [Service] # Needed to call setns() as ip netns does CapabilityBoundingSet=CAP_SYS_ADMIN And then created a netns@.service in /etc/systemd/system: [Unit] Description=network namespace %I [Service] Type=oneshot ExecStart=/bin/ip netns add %I ExecStop=/bin/ip netns del %I RemainAfterExit=yes By default, openvpn manually runs ifconfig or ip to set up its tun device. Luckily for us, you can configure openvpn to run a custom script instead. (though you have to set script-security >= 2. :( ) I named my script /usr/local/bin/vpn-ns, so here's the relevant snippet from my [openvpn configuration file][4]: # ... # (my other configuration) # ... # script should run ip, not openvpn route-noexec ifconfig-noexec up "/usr/local/bin/vpn-ns" route-up "/usr/local/bin/vpn-ns" script-security 2 Using [Sebastian's script][1] as a basis, I hacked together the following. Notice that it guesses the name of the network namespace based on the name of the instance's configuration file (e.g., /etc/openvpn/foo.conf→foo). #!/bin/bash # based heavily on http://naju.se/articles/openvpn-netns [[ $EUID -ne 0 ]] && { echo "$0: this program requires root privileges. try again with 'sudo'?" >&2 exit 1 } # convert a dot-decimal mask (e.g., 255.255.255.0) to a bit-count mask # (like /24) for iproute2. this probably isn't the most beautiful way. tobitmask() { bits=0 while read -rd . octet; do (( col = 2**7 )) while (( col > 0 )); do (( octet & col )) || break 2 (( bits++ )) (( col >>= 1 )) done done <<<"$1" echo$bits } # guess name of network namespace from name of config file basename="$(basename "$config")" ns="${basename%.conf}" netmask="$(tobitmask "$route_netmask_1")" case$script_type in up) ip -netns "$ns" link set dev lo up ip link set dev$dev up netns "$ns" mtu "$tun_mtu" ip -netns "$ns" addr add "$ifconfig_local/$netmask" dev "$dev" ;; route-up) ip -netns "$ns" route add default via "$route_vpn_gateway" ;; *) echo "$0: unknown \$script_type: '$script_type'" >&2 exit 2; ;; esac Now, once you've told systemd to start openvpn@foo, you can run any application you'd like under the new namespace:$ nsdo foo firefox Alternatively, if you don't want to bother with nsdo: $sudo ip netns foo sudo -u$USER firefox addendum: configuring veth -------------------------- *Note: if you're curious about veth, [Scott Lowe's handy blog post][5], where I found the commands below, serves as a good introduction.* Suppose that I want to use nsdo+openvpn as described above to tunnel an application that also provides a server (for RPC, for instance). That is, I run an application that binds to a port *in* a namespace, but I want to connect to it outside of that namespace. With the setup I've described up to this point, I simply cannot do this. Certainly, network namespaces separate running programs from one another -- an application can't cross the line willy-nilly. For instance, I could not use netcat to listen on a port in one namespace and then connect to it from another: $nsdo foo nc -l -p 5050 <<<"hi!" &$ nc -v localhost 5050 <<<"hello" localhost [127.0.0.1] 5050 (mmcc): Connection refused $nsdo foo !! hi! hello So (as far as I know) I have no other choice but to use veth, a kernel feature [designed][6] to allow network namespaces to communicate. veth interfaces act just like any interface but come in pairs -- one for each namespace. You can set them up with a systemd service like the following (I've named it foo-veth.service): [Unit] Description=veth for foo netns After=netns@foo.service [Service] Type=oneshot RemainAfterExit=yes # configure our end ExecStart=/usr/bin/ip link add ns-foo up type veth peer name ns-def netns foo ExecStart=/usr/bin/ip addr add 10.0.255.1/24 dev ns-foo # configure vpn end ExecStart=/usr/bin/ip -netns foo link set dev ns-def up ExecStart=/usr/bin/ip -netns foo addr add 10.0.255.2/24 dev ns-def # tear down everything ExecStop=/usr/bin/ip link del ns-foo [Install] WantedBy=netns@foo.service (Note: I've chosen not to make this example systemd service generic -- like netns-veth@.service -- because I currently use veth with only one vpn/namespace and I'm not sure how I'd assign unique IP addresses otherwise.) Now, make netns@foo start the new service automatically and then (this time) start it manually: # systemctl enable foo-veth # systemctl start foo-veth For convenience, make the name of the namespace resolve to the IP address assigned to its corresponding veth interface: # printf 'foo\t10.0.255.2\n' >>/etc/hosts Done! You can now reach servers running in namespaces by simply connecting to the namespace by name. If a hypothetical application listens on port 5050 in namespace foo, for instance, you can access it by pointing your client to foo:5050:$ curl foo:5050 Hello, world! [1]: http://naju.se/articles/openvpn-netns [2]: https://projects.archlinux.org/svntogit/packages.git/tree/trunk/openvpn@.service?h=packages/openvpn [3]: http://www.freedesktop.org/software/systemd/man/systemd.service.html [4]: https://community.openvpn.net/openvpn/wiki/Openvpn23ManPage [5]: http://blog.scottlowe.org/2013/09/04/introducing-linux-network-namespaces/ [6]: https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=e314dbdc1c0dc6a548ecf0afce28ecfd538ff568 [7]: https://github.com/OpenVPN/openvpn/blob/master/distro/systemd/openvpn-client%40.service [8]: http://manpages.ubuntu.com/manpages/xenial/en/man7/capabilities.7.html [9]: https://www.freedesktop.org/software/systemd/man/systemd.unit.html [10]: https://code.austinjadams.com/nsdo [11]: https://github.com/ausbin/nsdo#readme