特定のアプリがOpenVPN経由でインターネットにアクセスできるようにしたい。私はこの質問/スレッドの最後の最後の回答/コメントで解決策を見つけました: OpenVPNを介してすべてのトラフィックを特定のネットワーク名前空間にのみフィードする
名前空間内でOpenVPNリンクを開始し、名前空間内でそのOpenVPNリンクを使用するすべてのコマンドを実行できます。 (私の仕事ではなく)それを行う方法の詳細はこちら:
http://www.naju.se/articles/openvpn-netns.html
私はそれを試してみましたが、うまくいきました。アイデアは、グローバルではなく特定の名前空間内でOpenVPN接続のアップフェーズとルートアップフェーズを実行するカスタムスクリプトを提供することです。将来オフラインになる場合に備えて、上記のリンクから引用します。
First create an --up script for OpenVPN. This script will create the VPN tunnel interface inside a network namespace called vpn, instead of the default namespace.
$ cat > netns-up << EOF
#!/bin/sh
case $script_type in
up)
ip netns add vpn
ip netns exec vpn ip link set dev lo up
mkdir -p /etc/netns/vpn
echo "nameserver 8.8.8.8" > /etc/netns/vpn/resolv.conf
ip link set dev "$1" up netns vpn mtu "$2"
ip netns exec vpn ip addr add dev "$1" \
"$4/${ifconfig_netmask:-30}" \
${ifconfig_broadcast:+broadcast "$ifconfig_broadcast"}
test -n "$ifconfig_ipv6_local" && \
ip netns exec vpn ip addr add dev "$1" \
"$ifconfig_ipv6_local"/112
;;
route-up)
ip netns exec vpn ip route add default via "$route_vpn_gateway"
test -n "$ifconfig_ipv6_remote" && \
ip netns exec vpn ip route add default via \
"$ifconfig_ipv6_remote"
;;
down)
ip netns delete vpn
;;
esac
次に、OpenVPNを起動し、ifconfigとrouteを実行する代わりに--upスクリプトを使用するように指示します。
openvpn --ifconfig-noexec --route-noexec --up netns-up --route-up netns-up --down netns-up
これで、次のようにトンネリングするプログラムを開始できます。
ip netns exec vpn command
唯一の落とし穴は、ip netnsexecを呼び出すにはrootである必要があるということです...そしておそらくアプリケーションをrootとして実行したくないでしょう。解決策は簡単です:
Sudo ip netns exec vpn Sudo -u $(whoami) command
Netns-upスクリプトを呼び出すopenvpnコマンドを実行しようとすると、次の2つのエラーが発生します。
:/etc/openvpn$ Sudo openvpn --ifconfig-noexec --route-noexec --up netns-up --route-up netns-up --down netns-up --config za1.nordvpn.com.tcp443.ovpn
(..)
Tue Mar 22 00:10:56 2016 [vpn-za.nordvpn.com] Peer Connection Initiated with [AF_INET]154.127.61.142:443
Tue Mar 22 00:10:59 2016 SENT CONTROL [vpn-za.nordvpn.com]: 'Push_REQUEST' (status=1)
Tue Mar 22 00:10:59 2016 Push: Received control message: 'Push_REPLY,redirect-gateway def1,dhcp-option DNS 78.46.223.24,dhcp-option DNS 162.242.211.137,route 10.7.7.1,topology net30,ping 5,ping-restart 30,ifconfig 10.7.7.102 10.7.7.101'
Tue Mar 22 00:10:59 2016 OPTIONS IMPORT: timers and/or timeouts modified
Tue Mar 22 00:10:59 2016 OPTIONS IMPORT: --ifconfig/up options modified
Tue Mar 22 00:10:59 2016 OPTIONS IMPORT: route options modified
Tue Mar 22 00:10:59 2016 OPTIONS IMPORT: --ip-win32 and/or --dhcp-option options modified
Tue Mar 22 00:10:59 2016 ROUTE_GATEWAY 192.168.1.254/255.255.255.0 IFACE=eth0 HWADDR=b8:27:eb:39:7e:46
Tue Mar 22 00:10:59 2016 TUN/TAP device tun0 opened
Tue Mar 22 00:10:59 2016 TUN/TAP TX queue length set to 100
Tue Mar 22 00:10:59 2016 netns-up tun0 1500 1592 10.7.7.102 10.7.7.101 init
Tue Mar 22 00:10:59 2016 WARNING: Failed running command (--up/--down): external program exited with error status: 1
Tue Mar 22 00:10:59 2016 Exiting due to fatal error
Sudoを使用した場合と使用しない場合でnetns-upスクリプトを再作成しようとしましたが、役に立ちませんでした。何が悪いのですか?
ネットワーク名前空間内でopenvpnを開始する方が安全です。次のスクリプト(Schnoukiのフォーク)を使用して、名前空間を作成し、ファイアウォール、DNSを構成し、接続をテストし、openvpnを起動し、最後にトレントクライアントを起動します。必要に応じて調整する必要があるスクリプトにTODOを配置します。
#!/bin/sh
# start openvpn tunnel and torrent client inside Linux network namespace
#
# this is a fork of schnouki's script, see original blog post
# https://schnouki.net/posts/2014/12/12/openvpn-for-a-single-application-on-linux/
#
# original script can be found here
# https://Gist.github.com/Schnouki/fd171bcb2d8c556e8fdf
# ------------ adjust values below ------------
# network namespace
NS_NAME=myVPN
NS_EXEC="ip netns exec $NS_NAME"
# user for starting the torrent client
REGULAR_USER=heinzwurst
# ---------------------------------------------
# exit on unbound variable
set -u
# exit on error
set -e
set -o pipefail
# trace option
#set -x
if [ $USER != "root" ]; then
echo "This must be run as root."
exit 1
fi
start_vpn() {
echo "Add network interface"
# Create the network namespace
ip netns add $NS_NAME
# Start the loopback interface in the namespace
$NS_EXEC ip addr add 127.0.0.1/8 dev lo
$NS_EXEC ip link set lo up
# Create virtual network interfaces that will let OpenVPN (in the
# namespace) access the real network, and configure the interface in the
# namespace (vpn1) to use the interface out of the namespace (vpn0) as its
# default gateway
ip link add vpn0 type veth peer name vpn1
ip link set vpn0 up
ip link set vpn1 netns $NS_NAME up
ip addr add 10.200.200.1/24 dev vpn0
$NS_EXEC ip addr add 10.200.200.2/24 dev vpn1
$NS_EXEC ip link set dev vpn1 mtu 1492
$NS_EXEC ip route add default via 10.200.200.1 dev vpn1
# Configure the nameserver to use inside the namespace
# TODO use VPN-provided DNS servers in order to prevent leaks
mkdir -p /etc/netns/$NS_NAME
cat >/etc/netns/$NS_NAME/resolv.conf <<EOF || exit 1
nameserver 8.8.8.8
nameserver 8.8.4.4
EOF
# IPv4 NAT, you may need to adjust the interface name prefixes 'eth' 'wlan'
iptables -t nat -A POSTROUTING -o eth+ -m mark --mark 0x29a -j MASQUERADE
iptables -t nat -A POSTROUTING -o wlan+ -m mark --mark 0x29a -j MASQUERADE
iptables -t mangle -A PREROUTING -i vpn0 -j MARK --set-xmark 0x29a/0xffffffff
# TODO create firewall rules for your specific application (torrent)
# or just comment the line below
$NS_EXEC iptables-restore < /etc/iptables/iptables-$NS_NAME.rules
# we should have full network access in the namespace
$NS_EXEC ping -c 3 www.google.com
# start OpenVPN in the namespace
echo "Starting VPN"
cd /etc/openvpn
# TODO create openvpn configuration in /etc/openvpn/$NS_NAME.conf
$NS_EXEC openvpn --config $NS_NAME.conf &
# wait for the tunnel interface to come up
while ! $NS_EXEC ip link show dev tun0 >/dev/null 2>&1 ; do sleep .5 ; done
}
stop_vpn() {
echo "Stopping VPN"
ip netns pids $NS_NAME | xargs -rd'\n' kill
# TODO wait for terminate
# clear NAT
iptables -t nat -D POSTROUTING -o eth+ -m mark --mark 0x29a -j MASQUERADE
iptables -t nat -D POSTROUTING -o wlan+ -m mark --mark 0x29a -j MASQUERADE
iptables -t mangle -D PREROUTING -i vpn0 -j MARK --set-xmark 0x29a/0xffffffff
echo "Delete network interface"
rm -rf /etc/netns/$NS_NAME
ip netns delete $NS_NAME
ip link delete vpn0
}
# stop VPN on exit (even when error occured)
trap stop_vpn EXIT
start_vpn
# TODO start your favorite torrent client
$NS_EXEC Sudo -u $REGULAR_USER transmission-gtk
私は有線イーサネットでLANに接続されたコンピューターを持っているので、これで少し違う方法をとることができました:ブリッジング!これにより、LANのDHCPサーバーがホストとvpn名前空間に個別のアドレスを付与し、vpn名前空間にインターネットへの直接アクセスを許可します。 iptablesの変更や手動のIPアドレス指定は必要ありません!これがコマンドでの私の全体的な解決策です。
# Make the netns.
ip netns add vpn
ip link add br0 type bridge
ip link set br0 up
# Make the inter-namespace pipe and bridge the Host end.
ip link add veth.Host type veth peer veth.vpn
ip link set veth.Host master br0 up
ip link set veth.vpn netns vpn up
# Bridge the wired ethernet.
ip link set eth0 master br0 up
# Start dhcpcd on the bridge for the Host to use and on veth.vpn
# for the vpn netns to use. The router will grant separate IP
# addresses to both! (They have different MAC addresses.)
dhcpcd br0
ip netns exec vpn dhcpcd veth.vpn
# OK, now start the VPN.
ip netns exec vpn openvpn --config etc etc etc
かなりシンプルなセットアップ。
私はFelixが提供するフォークスクリプトを約6か月間使用しており、先週までは非常にうまく機能していました(Nehal JWaniによって提案されたsysctl-w net.ipv4.ip_forward = 1行を追加してコメントアウトした後) pipefail-それ以外の場合はクラッシュするため)。
それでも、先週、完全に機能しなくなったことに気づきました(Debian 9.4)。スクリプトは非常に便利なので、問題のある部分をデバッグするために多くの時間を費やしましたが、何を試しても、再び機能させることはできませんでした。
これはとても便利な機能なので、誰かが同じ問題に遭遇した場合にうまく機能するSchnoukiの作業の代替フォークを提供したいと思いました。
https://github.com/crasm/vpnshift.sh
#!/bin/bash
#
# Copyright (c) 2016, crasm <[email protected]>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
usage="usage: vpnshift -c <config> [<command> [<arg>...]]
optional:
-u <user> Execute <command> as <user>
-d Toggle namespace debug Shell
if not otherwise specified:
- The command defaults to the user's Shell (${Shell}).
- The user must be inferred from Sudo.
"
quick_die() {
format="$1"; shift
>&2 printf "${format}\n" "$@"
exit 1
}
die() {
format="$1"; shift
>&2 printf "${format}\n" "$@"
clean_exit 1
}
hush() {
eval "$@" > /dev/null 2> /dev/null
}
must() {
eval "$@" || die "failed: %s" "$*"
}
is_running() {
local pid="$1"
hush kill -s 0 "${pid}"
}
sig_kill() {
local pid="$1"
hush kill -s KILL "${pid}"
}
sig_term() {
local pid="$1"
hush kill -s TERM "${pid}"
}
clean_exit() {
local exit_code="$1"
if is_running "${openvpn_pid}"; then
# Kill openvpn.
sig_term "${openvpn_pid}"
>&2 printf "stopping openvpn (pid = %d)." "${openvpn_pid}"
for i in {1..100}; do
if is_running "${openvpn_pid}"; then
sleep 0.1
printf "."
else
break
fi
done
printf "\n"
if is_running "${openvpn_pid}"; then
>&2 echo "forced to kill openvpn"
sig_kill "${openvpn_pid}"
fi
else
>&2 echo "openvpn exited"
fi
# don't start cleaning up until openvpn is gone
hush ip netns delete "${namespace}"
hush rm --recursive --force "${namespace_dir}"
hush sysctl --quiet net.ipv4.ip_forward="${forward}"
if hush ps -C 'firewalld'; then
echo "[firewalld] clearing firewalld state"
hush systemctl restart firewalld
else
echo "${rules}" | hush iptables-restore
fi
# Sometimes there's a lag for the veths to be deleted by linux, so we
# delete it manually.
hush ip link delete "${veth_default}"
hush ip link delete "${veth_vpn}"
exit "${exit_code}"
}
nsdo() {
ip netns exec "${namespace}" "$@"
}
_debug=0
main() {
local config=
local user="${Sudo_USER}"
while getopts "hdc:u:" opt; do
case "${opt}" in
h) quick_die "${usage}" ;;
d) _debug=1 ;;
c) config="$(realpath "${OPTARG}")" ;;
u) user="${OPTARG}" ;;
*) quick_die "unknown option: %s" "${opt}" ;;
esac
done
shift $(( OPTIND - 1 ))
if [[ -z "${config}" ]]; then
quick_die "openvpn config is required"
fi
if [[ -z "${user}" ]]; then
quick_die "user must be provided explicitly via '-u' or implicitly via Sudo_USER"
fi
local cmd="$1"; shift
if [[ -z "${cmd}" ]]; then
cmd="${Shell}"
fi
must ip netns add vpnshift
must mkdir --parents "${namespace_dir}"
# Set up loopback interface
must nsdo ip address add '127.0.0.1/8' dev lo
must nsdo ip address add '::1/128' dev lo
must nsdo ip link set lo up
# Set up veth tunnel
must ip link add "${veth_vpn}" type veth peer name "${veth_default}"
must ip link set "${veth_vpn}" netns "${namespace}"
must ip link set "${veth_default}" up
must nsdo ip link set "${veth_vpn}" up
must ip address add "10.10.10.10/31" dev "${veth_default}"
must nsdo ip \
address add "10.10.10.11/31" dev "${veth_vpn}"
must nsdo ip \
route add default via "10.10.10.10" dev "${veth_vpn}"
# Set up NAT and IP forwarding
must sysctl --quiet net.ipv4.ip_forward=1
# check if we need to enable masquerading via firewalld for veth_default
if hush ps -C 'firewalld'; then
echo "[firewalld] enabling firewalld based masquerading for ${veth_default}"
if [[ $(firewall-cmd --get-zones | grep "${namespace}") != *"${namespace}"* ]]
then
echo "[firewalld] creating permanent new zone ${namespace} with target default"
must firewall-cmd -q --permanent --new-zone="${namespace}"
must firewall-cmd -q --permanent --zone="${namespace}" --set-target="default"
must firewall-cmd -q --reload
fi
# add interface to our zone
echo "[firewalld] adding ${veth_default} and ${veth_vpn} to zone ${namespace}"
must firewall-cmd -q --zone="${namespace}" --change-interface="${veth_default}"
# apply our source range to our zone
echo "[firewalld] adding 10.10.10.10/31 as source for ${namespace}"
must firewall-cmd -q --zone="${namespace}" --add-source=10.10.10.10/31
# enable masquerading from our new source range on the default zone
default_zone=$(firewall-cmd --get-default-zone)
echo "[firewalld] enabling masquerading on default zone: ${default_zone}"
must firewall-cmd -q --zone="${default_zone}" --add-masquerade
must firewall-cmd -q --zone="${default_zone}" --add-rich-rule=\'rule family="ipv4" source address="10.10.10.10/31" masquerade\'
# optionally allow ports, services, etc. on our zone
# enabling desired ports
#echo "enabling all port traffic on zone ${namespace}"
#must firewall-cmd -q --zone="${namespace}" --add-port=1025-65535/udp
#must firewall-cmd -q --zone="${namespace}" --add-port=1025-65535/tcp
# enable services
#echo "enabling dns on zone ${namespace}"
#must firewall-cmd -q --zone="${namespace}" --add-service=dns
else
must iptables --table "nat" --append "POSTROUTING" --jump "MASQUERADE" --source "10.10.10.10/31"
fi
# Set up DNS inside the new namespace
printf > "${namespace_dir}/resolv.conf" \
"nameserver %s\nnameserver %s\n" \
"108.62.19.131" \
"104.238.194.235"
# drop in a Shell to debug namespace connectivity ... the exit trap will catch exit from this and clean up
if [[ "$_debug" == 1 ]]; then
nsdo "${Shell}"
fi
# Launch openvpn
local tun="tunvpn"
nsdo openvpn \
--cd "$(dirname "${config}")" \
--config "${config}" \
--dev "${tun}" \
--errors-to-stderr &
openvpn_pid=$(ps --ppid "$!" \
--format "pid" \
--no-headers
)
>&2 printf "waiting for openvpn (pid = %d)\n" "${openvpn_pid}"
while ! hush nsdo ip link show "${tun}"; do
if ! is_running "${openvpn_pid}"; then
clean_exit 1
fi
sleep 0.2
done
# Removing the default route protects from exposure if openvpn exits
# prematurely.
must nsdo ip \
route delete default via "10.10.10.10" dev "${veth_vpn}"
nsdo Sudo -u "${user}" "${cmd}" "$@"
}
if [[ $# == 0 ]]; then
quick_die "${usage}"
Elif [[ "$(id -u)" != 0 ]]; then
Sudo "$0" "$@"
exit "$?"
fi
# Stuff needed by clean_exit() to restore previous state.
namespace="vpnshift"
namespace_dir="/etc/netns/${namespace}"
forward="$(sysctl --values "net.ipv4.ip_forward")"
rules="$(iptables-save -t nat)"
veth_default="veth_default"
veth_vpn="veth_vpn"
openvpn_pid= # This is set later.
# Enable cleanup routine.
trap 'clean_exit 1' INT TERM
trap 'clean_exit $?' EXIT
main "$@"