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


16

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

OpenVPNを通常どおりに使用する(つまり、クライアントからのすべてのトラフィックをVPNに送り込む)場合、正常に機能します。具体的には、次のように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)インターフェースのみをネットワーク名前空間に割り当てることができる(まだ)ことを前提に(これは、真に不当に不必要なAPI制限に対して今年の賞を受賞)、vethペアとブリッジを作成し、名前空間にvethペアの一方の端を配置します。これは、床にトラフィックを落とすことさえしません:それは、私が橋にトンネルを入れることを許しません![編集:これは、タップデバイスのみをブリッジに配置できるためです。ネットワーク名前空間に任意のデバイスを配置できないこととは異なり、実際には理にかなっています。ブリッジとはイーサネット層の概念です。残念ながら、私の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.gzたがCONFIG_XEN_TMEM、私は実際にはありません持って、そのモジュールを(カーネルが私のファイルシステムの外部に格納されています。/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>

明白なことから始めましょう:vethデバイスはサポートされていますか?カーネルモジュール(veth)はロードされていますか?
カウンター

@countermodeにgrep veth /proc/modulesは何もリストされていませんが、それが決定的かどうかはわかりません。LinodeインスタンスにはOSパーティション内にカーネルがインストールされていないため、とにかく欠落しているモジュールをロードできるかどうかはわかりません。
zwol 14

lsmodすべての任意の出力を生成?ディレクトリはあり/lib/modulesますか?
カウンター

lsmod: command not found。がありますが、モジュール/lib/modulesはありません。空のファイルを含むカーネルごとのディレクトリの束だけです。Linode固有のヘルプを調べて、それがどのようになっているかを確認します。modules.dep
zwol

うーん...非常に奇妙です。私はLinodeに慣れていませんが、vethデバイスがサポートされていないように見えます。
カウンター

回答:


9

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

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

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

最初に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 ...、おそらくアプリケーションをルートとして実行したくないことです。解決策は簡単です。

sudo ip netns exec vpn sudo -u $(whoami)コマンド

1
こんにちは、サイトへようこそ!ユーザーは、回答に貼り付けるリンクの内容を少なくとも(可能であれば)要約することをお勧めします。これにより、リンクが古くなった場合(たとえば、サイトにアクセスできなくなった場合)に回答の品質を保持できます。リンクされた記事の最も重要な部分/指示を含めて、回答を改善してください。
エラティエル

これはすばらしいことですが、シェルがすべての変数を展開するのを防ぐために、開始ヒアドキュメントの区切り文字を単一引用符で囲む必要があります。
-ewatt

7

それはあなたがいることが判明することができ、ネットワークの名前空間にトンネルインターフェイスを置きます。私のすべての問題は、インターフェースを起動する際の間違いにありました。

ip addr add dev $tun_tundv \
    local $ifconfig_local/$ifconfig_cidr \
    broadcast $ifconfig_broadcast \
    scope link

問題は「スコープリンク」であり、ルーティングにのみ影響を与えると誤解しています。これにより、カーネルはトンネルに送信されるすべてのパケットの送信元アドレスを0.0.0.0ます。おそらく、OpenVPNサーバーはRFC1122に従って無効として破棄します。応答しなかったとしても、宛先は明らかに返信できません。

ネットワーク名前空間がなくてもすべてが正常に機能しました。これは、openvpnの組み込みネットワーク構成スクリプトがこの間違いを犯さなかったためです。「スコープリンク」がなくても、元のスクリプトも機能します。

(どうやってこれを発見したのですか?straceopenvpnプロセスを実行し、トンネル記述子から読み取ったものをすべてhexdumpに設定してから、パケットヘッダーを手動でデコードします。)


これに関するガイドを書くことができますか?私は似たようなものを設定しようとしていますが、あなたの質問のどの部分から始めて良いのか、どの部分が失敗につながったのかを判断するのは難しいです。
震災14

@tremby近い将来にそうする時間はないでしょうが、github.com / zackw / tbbscraper / blob / master / scripts / openvpn-netns.cが役に立つかもしれません。
zwol 14

うん、1100行のCプログラムが役立つかどうかわかりません。最終的な設定、スクリプト、呪文だけで仕事は完了しましたか?...または、そのCプログラムがこれを最終的に実装していますか?
震災14

@trembyはい、そのCプログラムは私の最終的な実装です。(私の使用シナリオでは、setuidである必要があります。)あなたはただ物を落とすことができるかもしれません-一番上の大きなコメントがそれを使用する方法を説明しないなら、私に知らせてください。
zwol

代替で@tremby、「内部のopenvpnから実行スクリプト」を見て、で始まるgithub.com/zackw/tbbscraper/blob/master/scripts/...ネットワークの名前空間を設定し、解体されたかを確認するために、。そして、ovpnクライアントの実際の呼び出しはであるgithub.com/zackw/tbbscraper/blob/master/scripts/...。コードの残りの部分は、これらの操作を書くのが面倒にならないようにするミニシェルの実装と考えることができます。
zwol

4

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

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

ip link add name veth0 type veth peer name veth1

(のnameinstad dev

では、名前空間からVPNトンネルへのトラフィックを取得する方法は?自由に使用できるのはチューニングデバイスのみであるため、「ホスト」がルーティングする必要があります。すなわち、vethペアを作成し、ネームスペースに1つ入れます。トンネルへのルーティングを介してもう一方を接続します。したがって、転送を有効にしてから、必要なルートを追加します。

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

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

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

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も「ゲスト」ネットも妨害されないように、パケットフィルタールールによってそれを強化することができます。

NB tun0そもそも名前空間に移動するのは正しいことのように見えます。しかし、あなたのように、私はそれを働かせませんでした。ポリシールーティングは、次に行うべきことのように見えます。あなたは、VPNの背後にあるネットワークを知っていればマヘンドラのソリューションを適用することが可能である他のすべてのアプリケーションは、これらのネットワークにアクセスすることはありません。しかし、初期条件(「特定のプロセスとの間のすべてのトラフィック、および特定のプロセスからのトラフィックのみがVPNを通過する」)は、後者を保証できないかのように聞こえます。


おかげで、これでもう少し先に進みますが、今は「そして、ブリッジを使用してvethデバイスをトンネルに接続する」という部分に固執しています。修正された質問をご覧ください。
zwol 14

投稿したばかりの回答によると、すべてが元のスクリプトの愚かな間違いに帰着します。「スコープリンク」は、私が意図したことを意味するものではありません。しかし、私はさまざまな可能性を試すのにあなたが多くの仕事を費やしたので、私はあなたに報奨金を差し上げます。
zwol

ちょっとザック、どうもありがとう。名前空間とポリシールーティングは、研究するのに興味深いものでした。それ自体がエキサイティングでなければ、私はこれにそれほど努力しなかっただろう。
カウンター

0

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. 両方のネームサーバーエントリ(resolv.conf内)をVPNおよび直接接続用に追加します。

これで、すべてのnet1およびnet2接続がVPNを通過し、リセットが直接(この例ではwlo1を介して)行われます。


残念ながら、VPNを介してアクセスされるネットワークは事前に知られていないため、これは機能しません。
zwol
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.