web-dev-qa-db-ja.com

OpenVPNを介してすべてのトラフィックを特定のネットワーク名前空間のみにフィードする

特定のプロセスとの間のすべてのトラフィックとのみトラフィックが通過するように(OpenVPNを使用して)VPNをセットアップしようとしていますVPN;他のプロセスは、物理デバイスを直接使用し続ける必要があります。 Linuxでこれを行う方法はネットワーク名前空間を使用することだと私は理解しています。

OpenVPNを通常使用する場合(つまり、VPNを介したクライアントからのファネリングallトラフィック)、それは正常に動作します。具体的には、OpenVPNを次のように起動します。

# openvpn --config destination.ovpn --auth-user-pass credentials.txt

(destination.ovpnの編集バージョンはこの質問の最後にあります。)

トンネルデバイスを名前空間に制限するスクリプトを記述して、次のステップに行き詰まっています。私が試してみました:

  1. トンネルデバイスを名前空間に直接配置する

    # ip netns add tns0
    # ip link set dev tun0 netns tns0
    # ip netns exec tns0 ( ... commands to bring up tun0 as usual ... )
    

    これらのコマンドは正常に実行されますが、名前空間内で生成されたトラフィック(例:ip netns exec tns0 traceroute -n 8.8.8.8)はブラックホールに分類されます。

  2. 仮想イーサネット(veth)インターフェイスのみをネットワーク名前空間に割り当てることができる 」という前提で(これがtrueの場合、非常に不必要なAPIに対して今年の賞を受賞します)制限)、vethペアとブリッジを作成し、vethペアの一方の端を名前空間に配置します。これは、床にトラフィックを落とすことさえできません。トンネルを橋に入れることはできません! [編集:これは、ブリッジに入れることができるのはtapデバイスのみであるためです。任意のデバイスをネットワーク名前空間に配置できないこととは異なり、これは実際には理にかなっています。これは、ブリッジがイーサネット層の概念であるためです。残念ながら、私のVPNプロバイダーはタップモードのOpenVPNをサポートしていないため、回避策が必要です。]

    # ip addr add dev tun0 local 0.0.0.0/0 scope link
    # ip link set tun0 up
    # ip link add name teo0 type veth peer name tei0
    # ip link set teo0 up
    # brctl addbr tbr0
    # brctl addif tbr0 teo0
    # brctl addif tbr0 tun0
    can't add tun0 to bridge tbr0: Invalid argument
    

この質問の最後にあるスクリプトは、vethアプローチ用です。ダイレクトアプローチのスクリプトは、編集履歴にあります。最初に設定せずに使用されているように見えるスクリプト内の変数は、openvpnプログラムによって環境に設定されます-はい、それはずさんで小文字の名前を使用します。

これを機能させる方法について具体的なアドバイスを提供してください。私はここでカーゴカルトでプログラミングしていることを痛感しています-誰かがこれについて包括的なドキュメントを書いていますか?何も見つかりません-したがって、スクリプトの一般的なコードレビューもありがたいです。

重要な場合:

# uname -srvm
Linux 3.14.5-x86_64-linode42 #1 SMP Thu Jun 5 15:22:13 EDT 2014 x86_64
# openvpn --version | head -1
OpenVPN 2.3.2 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [EPOLL] [PKCS11] [eurephia] [MH] [IPv6] built on Mar 17 2014
# ip -V
ip utility, iproute2-ss140804
# brctl --version
bridge-utils, 1.5

カーネルは私の仮想ホスティングプロバイダー( Linode )によって構築され、CONFIG_MODULES=yでコンパイルされていますが、実際のモジュールはありません-CONFIG_*変数セットのみへのmによると/proc/config.gzCONFIG_XEN_TMEMであり、実際にはhaveそのモジュール(カーネルは私のファイルシステムの外部に保存されます。/lib/modulesは空で、/proc/modulesはそれが何らかの方法で魔法のようにロードされなかったことを示します)。リクエストに応じて提供された/proc/config.gzからの抜粋ですが、ここにすべてを貼り付けたくありません。

netns-up.sh

#! /bin/sh

mask2cidr () {
    local nbits dec
    nbits=0
    for dec in $(echo $1 | sed 's/\./ /g') ; do
        case "$dec" in
            (255) nbits=$(($nbits + 8)) ;;
            (254) nbits=$(($nbits + 7)) ;;
            (252) nbits=$(($nbits + 6)) ;;
            (248) nbits=$(($nbits + 5)) ;;
            (240) nbits=$(($nbits + 4)) ;;
            (224) nbits=$(($nbits + 3)) ;;
            (192) nbits=$(($nbits + 2)) ;;
            (128) nbits=$(($nbits + 1)) ;;
            (0)   ;;
            (*) echo "Error: $dec is not a valid netmask component" >&2
                exit 1
                ;;
        esac
    done
    echo "$nbits"
}

mask2network () {
    local Host mask h m result
    Host="$1."
    mask="$2."
    result=""
    while [ -n "$Host" ]; do
        h="${Host%%.*}"
        m="${mask%%.*}"
        Host="${Host#*.}"
        mask="${mask#*.}"
        result="$result.$(($h & $m))"
    done
    echo "${result#.}"
}

maybe_config_dns () {
    local n option servers
    n=1
    servers=""
    while [ $n -lt 100 ]; do
       eval option="\$foreign_option_$n"
       [ -n "$option" ] || break
       case "$option" in
           (*DNS*)
               set -- $option
               servers="$servers
nameserver $3"
               ;;
           (*) ;;
       esac
       n=$(($n + 1))
    done
    if [ -n "$servers" ]; then
        cat > /etc/netns/$tun_netns/resolv.conf <<EOF
# name servers for $tun_netns
$servers
EOF
    fi
}

config_inside_netns () {
    local ifconfig_cidr ifconfig_network

    ifconfig_cidr=$(mask2cidr $ifconfig_netmask)
    ifconfig_network=$(mask2network $ifconfig_local $ifconfig_netmask)

    ip link set dev lo up

    ip addr add dev $tun_vethI \
        local $ifconfig_local/$ifconfig_cidr \
        broadcast $ifconfig_broadcast \
        scope link
    ip route add default via $route_vpn_gateway dev $tun_vethI
    ip link set dev $tun_vethI mtu $tun_mtu up
}

PATH=/sbin:/bin:/usr/sbin:/usr/bin
export PATH

set -ex

# For no good reason, we can't just put the tunnel device in the
# subsidiary namespace; we have to create a "virtual Ethernet"
# device pair, put one of its ends in the subsidiary namespace,
# and put the other end in a "bridge" with the tunnel device.

tun_tundv=$dev
tun_netns=tns${dev#tun}
tun_bridg=tbr${dev#tun}
tun_vethI=tei${dev#tun}
tun_vethO=teo${dev#tun}

case "$tun_netns" in
     (tns[0-9] | tns[0-9][0-9] | tns[0-9][0-9][0-9]) ;;
     (*) exit 1;;
esac

if [ $# -eq 1 ] && [ $1 = "INSIDE_NETNS" ]; then
    [ $(ip netns identify $$) = $tun_netns ] || exit 1
    config_inside_netns
else

    trap "rm -rf /etc/netns/$tun_netns ||:
          ip netns del $tun_netns      ||:
          ip link del $tun_vethO       ||:
          ip link set $tun_tundv down  ||:
          brctl delbr $tun_bridg       ||:
         " 0

    mkdir /etc/netns/$tun_netns
    maybe_config_dns

    ip addr add dev $tun_tundv local 0.0.0.0/0 scope link
    ip link set $tun_tundv mtu $tun_mtu up

    ip link add name $tun_vethO type veth peer name $tun_vethI
    ip link set $tun_vethO mtu $tun_mtu up

    brctl addbr $tun_bridg
    brctl setfd $tun_bridg 0
    #brctl sethello $tun_bridg 0
    brctl stp $tun_bridg off

    brctl addif $tun_bridg $tun_vethO
    brctl addif $tun_bridg $tun_tundv
    ip link set $tun_bridg up

    ip netns add $tun_netns
    ip link set dev $tun_vethI netns $tun_netns
    ip netns exec $tun_netns $0 INSIDE_NETNS

    trap "" 0
fi

netns-down.sh

#! /bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin
export PATH

set -ex

tun_netns=tns${dev#tun}
tun_bridg=tbr${dev#tun}

case "$tun_netns" in
     (tns[0-9] | tns[0-9][0-9] | tns[0-9][0-9][0-9]) ;;
     (*) exit 1;;
esac

[ -d /etc/netns/$tun_netns ] || exit 1

pids=$(ip netns pids $tun_netns)
if [ -n "$pids" ]; then
    kill $pids
    sleep 5
    pids=$(ip netns pids $tun_netns)
    if [ -n "$pids" ]; then
        kill -9 $pids
    fi
fi

# this automatically cleans up the the routes and the veth device pair
ip netns delete "$tun_netns"
rm -rf /etc/netns/$tun_netns

# the bridge and the tunnel device must be torn down separately
ip link set $dev down
brctl delbr $tun_bridg

destination.ovpn

client
auth-user-pass
ping 5
dev tun
resolv-retry infinite
nobind
persist-key
persist-tun
ns-cert-type server
verb 3
route-metric 1
proto tcp
ping-exit 90
remote [REDACTED]
<ca>
[REDACTED]
</ca>
<cert>
[REDACTED]
</cert>
<key>
[REDACTED]
</key>
16
zwol

名前空間内でOpenVPNリンクを開始し、名前空間内でそのOpenVPNリンクを使用するすべてのコマンドを実行できます。 (私の仕事ではなく)それを行う方法の詳細はこちら:

http://www.naju.se/articles/openvpn-netns.html

私はそれを試してみましたが、うまくいきました。アイデアは、グローバルな名前空間ではなく、特定の名前空間内でOpenVPN接続のupおよびroute-upフェーズを実行するカスタムスクリプトを提供することです。将来オフラインになる場合に備えて、上記のリンクから引用します。

まず、OpenVPN用の--upスクリプトを作成します。このスクリプトは、デフォルトのネームスペースではなく、vpnと呼ばれるネットワークネームスペース内にVPNトンネルインターフェースを作成します。

$ 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
EOF

次に、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 netns exec ...を呼び出すにはrootである必要があり、アプリケーションをrootとして実行したくない場合です。解決策は簡単です:

Sudo ip netns exec vpn Sudo -u $(whoami)コマンド
9
Asuranceturix

Vethデバイスを作成しようとしたときのエラーは、ipがコマンドライン引数を解釈する方法の変更が原因です。

Vethデバイスのペアを作成するためのipの正しい呼び出しは、

ip link add name veth0 type veth peer name veth1

name in_stad of dev

では、名前空間からVPNトンネルにトラフィックを送信する方法を教えてください。調整できるのはtunデバイスだけなので、「ホスト」はルーティングする必要があります。つまりvethペアを作成し、1つを名前空間に配置します。もう一方をルーティング経由でトンネルに接続します。したがって、転送を有効にしてから、必要なルートを追加します。

例として、eth0がメインインターフェース、tun0がVPNトンネルインターフェース、veth0/veth1veth1が名前空間にあるインターフェースのペアであるとします。名前空間内では、veth1のデフォルトルートのみを追加します。

ポリシールーティングを採用する必要があるホストでは、たとえば here を参照してください。するべきこと:

次のようなエントリを追加/追加

1   vpn

/etc/iproute2/rt_tablesに。これにより、(まだ作成されていない)テーブルを名前で呼び出すことができます。

次に、次のステートメントを使用します。

ip rule add iif veth0 priority 1000 table vpn
ip rule add iif tun0 priority 1001 table vpn
ip route add default via <ip-addr-of-tun0> table vpn
ip route add <ns-network> via <ip-addr-of-veth0> table vpn

私はあなたのような設定でそれをここで試すことはできませんが、これはあなたが望むことを正確に行うはずです。パケットフィルタールールによって、vpnも「ゲスト」ネットも妨害されないように、それを補強することができます。

N.B.最初にtun0を名前空間に移動することは、正しいことのように見えます。しかし、あなたのように私はそれを働かせませんでした。ポリシールーティングは、次に行うべき正しいことのように見えます。 Mahendraのソリューションは、VPNの背後にあるネットワークがわかっている場合に適用でき、他のすべてのアプリケーションがこれらのネットワークにアクセスすることはありません。ただし、初期状態(「すべてのトラフィック、およびのみトラフィック、特定のプロセスとの間のVPNを経由する」)は、後者が保証されないかのように聞こえます。

4
countermode

VPNを介してアクセスするネットワークがわかっている場合は、ルーティングテーブルを編集して目的の結果を得ることができます。

  1. 現在のデフォルトルートをメモします。

    # ip route | grep default default via 192.168.43.1 dev wlo1 proto static metric 1024

  2. VPNを実行すると、ルーティングエントリが導入されます。

  3. 現在のデフォルトルート(VPNによって追加されます)を削除します。以前のデフォルトルートとして、テーブルの最初のデフォルトエントリになります。

    # ip route | grep default default dev tun0 scope link default via 192.168.43.1 dev wlo1 proto static metric 1024

    # ip route del default dev tun0 scope link

  4. VPN内のネットワークにカスタムルートを追加して、tun0経由でルーティングします。

    # ip route add <net1>/16 dev tun0

    # ip route add <net2>/24 dev tun0

  5. VPNと直接接続用のネームサーバーエントリ(resolv.conf内)の両方を追加します。

これで、すべてのnet1およびnet2接続はVPNを経由し、リセットは直接(この例ではwlo1を経由)になります。

0
mahendra