Umpan semua lalu lintas melalui OpenVPN hanya untuk namespace jaringan tertentu


16

Saya mencoba mengatur VPN (menggunakan OpenVPN) sedemikian rupa sehingga semua lalu lintas, dan hanya lalu lintas, ke / dari proses tertentu berjalan melalui VPN; proses lain harus terus menggunakan perangkat fisik secara langsung. Ini adalah pemahaman saya bahwa cara untuk melakukan ini di Linux adalah dengan ruang nama jaringan.

Jika saya menggunakan OpenVPN secara normal (yaitu menyalurkan semua lalu lintas dari klien melalui VPN), itu berfungsi dengan baik. Secara khusus, saya memulai OpenVPN seperti ini:

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

(Versi tujuan.ovpn yang sudah dihapus ada di akhir pertanyaan ini.)

Saya terjebak pada langkah berikutnya, menulis skrip yang membatasi perangkat terowongan ke ruang nama. Saya telah mencoba:

  1. Menempatkan perangkat terowongan langsung di namespace dengan

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

    Perintah-perintah ini dijalankan dengan sukses, tetapi lalu lintas yang dihasilkan di dalam namespace (misalnya dengan ip netns exec tns0 traceroute -n 8.8.8.8) jatuh ke dalam lubang hitam.

  2. Dengan asumsi bahwa " Anda [masih] hanya dapat menetapkan antarmuka Ethernet (veth) virtual ke ruang nama jaringan " (yang, jika benar, mengambil penghargaan tahun ini untuk pembatasan API yang sangat tidak perlu), membuat pasangan veth dan jembatan, dan menempatkan salah satu ujung pasangan veth di namespace. Ini bahkan tidak sampai menjatuhkan lalu lintas di lantai: itu tidak akan membiarkan saya memasukkan terowongan ke jembatan! [EDIT: Ini sepertinya karena hanya perangkat tap yang dapat dimasukkan ke jembatan. Berbeda dengan ketidakmampuan untuk menempatkan perangkat yang sewenang-wenang ke dalam namespace jaringan, yang sebenarnya masuk akal, apalagi dengan jembatan yang menjadi konsep Ethernet-layer; sayangnya, penyedia VPN saya tidak mendukung OpenVPN dalam mode tap, jadi saya perlu solusinya.]

    # 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
    

Skrip pada akhir pertanyaan ini adalah untuk pendekatan veth. Skrip untuk pendekatan langsung dapat ditemukan dalam riwayat edit. Variabel dalam skrip yang tampaknya digunakan tanpa mengaturnya terlebih dahulu diatur dalam lingkungan oleh openvpnprogram - ya, itu ceroboh dan menggunakan nama huruf kecil.

Harap berikan saran khusus tentang cara agar ini berfungsi. Saya sangat menyadari bahwa saya memprogram dengan pemujaan kargo di sini - adakah yang menulis dokumentasi komprehensif untuk hal ini? Saya tidak dapat menemukan - jadi review kode umum dari skrip juga dihargai.

Dalam hal itu penting:

# 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

Kernel dibangun oleh penyedia hosting virtual saya ( Linode ) dan, walaupun dikompilasi dengan CONFIG_MODULES=y, tidak memiliki modul yang sebenarnya - satu-satunya CONFIG_*variabel yang diatur mmenurut /proc/config.gzadalah CONFIG_XEN_TMEM, dan saya tidak benar - benar memiliki modul itu (kernel disimpan di luar sistem file saya; /lib/moduleskosong, dan /proc/modulesmenunjukkan bahwa itu entah bagaimana tidak dimuat secara ajaib). Kutipan dari yang /proc/config.gzdisediakan berdasarkan permintaan, tetapi saya tidak ingin menempel semuanya di sini.

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>

Mari kita mulai dengan yang sudah jelas: apakah perangkat didukung? Apakah modul kernel (veth) dimuat?
countermode

@countermode grep veth /proc/modulestidak mencantumkan apa pun, tapi saya tidak tahu apakah itu meyakinkan. Instance Linode tidak memiliki kernel yang diinstal di dalam partisi OS, jadi saya tidak yakin saya dapat memuat modul yang hilang.
zwol

Apakah lsmodmenghasilkan output sama sekali? Apakah ada direktori /lib/modules?
countermode

lsmod: command not found. Ada /lib/modules, tetapi tidak memiliki modul di dalamnya, hanya sekelompok direktori per-kernel yang berisi modules.depfile kosong . Saya akan mencari-cari bantuan Linode khusus dan mencari tahu apakah itu seharusnya.
zwol

hmm ... sangat aneh. Saya tidak terbiasa dengan Linode tetapi bagi saya sepertinya perangkat veth tidak didukung.
countermode

Jawaban:


9

Anda bisa memulai tautan OpenVPN di dalam namespace dan kemudian menjalankan setiap perintah yang Anda ingin gunakan tautan OpenVPN di dalam namespace. Detail tentang cara melakukannya (bukan pekerjaan saya) di sini:

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

Saya mencobanya dan berhasil; idenya adalah untuk menyediakan skrip khusus untuk melakukan fase naik dan naik-naik koneksi OpenVPN di dalam namespace tertentu alih-alih yang global. Saya mengutip dari tautan di atas kalau-kalau offline di masa depan:

Pertama buat skrip --up untuk OpenVPN. Skrip ini akan membuat antarmuka terowongan VPN di dalam namespace jaringan yang disebut vpn, alih-alih namespace default.

$ 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

Kemudian mulai OpenVPN dan katakan untuk menggunakan skrip --up kami alih-alih mengeksekusi ifconfig dan rute.

openvpn --ifconfig-noexec --route-noexec --up netns-up --route-up netns-up --down netns-up

Sekarang Anda dapat memulai program yang akan digali seperti ini:

ip netns exec vpn command

Satu-satunya tangkapan adalah bahwa Anda harus menjadi root untuk memanggil ip netns exec ...dan mungkin Anda tidak ingin aplikasi Anda dijalankan sebagai root. Solusinya sederhana:

sudo ip netns exec perintah vpn sudo -u $ (whoami)

1
Halo dan selamat datang di situs ini! Kami mendorong pengguna untuk setidaknya meringkas (jika mungkin) isi tautan yang mereka tempelkan dalam jawaban. Ini membantu mempertahankan kualitas jawaban seandainya tautannya menjadi basi (misalnya situs tidak lagi dapat diakses). Harap tingkatkan jawaban Anda dengan memasukkan bagian / instruksi terpenting dari artikel yang ditautkan.
Erathiel

Ini bagus, tetapi Anda perlu menempatkan tanda kutip tunggal di sekitar pembatas heredoc pembuka untuk mencegah shell dari memperluas semua variabel.
ewatt

7

Ternyata Anda dapat menempatkan antarmuka terowongan ke namespace jaringan. Seluruh masalah saya karena kesalahan dalam membuka antarmuka:

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

Masalahnya adalah "scope link", yang saya salah pahami hanya mempengaruhi routing. Ini menyebabkan kernel untuk mengatur alamat sumber dari semua paket yang dikirim ke terowongan 0.0.0.0; mungkin server OpenVPN kemudian akan membuangnya sebagai tidak valid per RFC1122; bahkan jika tidak, tujuan jelas tidak dapat menjawab.

Semuanya berfungsi dengan benar tanpa adanya ruang nama jaringan karena skrip konfigurasi jaringan bawaan openvpn tidak membuat kesalahan ini. Dan tanpa "tautan ruang lingkup", skrip asli saya juga berfungsi.

(Bagaimana saya menemukan ini, Anda bertanya? Dengan menjalankan stracepada proses openvpn, setel ke hexdump semua yang dibacanya dari descriptor tunnel, dan kemudian secara manual decoding header paket.)


Apakah ada kemungkinan Anda bisa menulis panduan tentang ini? Saya mencoba membuat sesuatu yang serupa tetapi sulit untuk mengatakan bagian mana dari pertanyaan Anda yang baik untuk memulai dan jalur mana yang mengarah pada kegagalan.
gemetar

@tremby Saya sepertinya tidak akan punya waktu untuk melakukan itu dalam waktu dekat, tetapi Anda mungkin menemukan github.com/zackw/tbbscraper/blob/master/scripts/openvpn-netns.c berguna.
zwol

Ya, saya tidak yakin program C 1100-line akan membantu. Bagaimana dengan konfigurasi terakhir, skrip dan mantera yang menyelesaikan pekerjaan untuk Anda? ... Atau apakah program C implementasi akhir Anda dari ini?
gemetar

@tremby Ya, program C itu adalah implementasi terakhir saya. (Dalam skenario penggunaan saya, itu harus setuid, Anda tahu.) Anda mungkin bisa langsung memasukkannya - jika komentar besar di atas tidak menjelaskan cara menggunakannya, beri tahu saya.
zwol

@tremby Dalam alternatifnya, lihat "Script yang dieksekusi dari dalam openvpn", mulai dari github.com/zackw/tbbscraper/blob/master/scripts/… , untuk melihat bagaimana namespace jaringan diatur dan dihancurkan; dan permintaan sebenarnya dari klien ovpn adalah di github.com/zackw/tbbscraper/blob/master/scripts/… . Sisa kode dapat dianggap sebagai implementasi shell-mini untuk membuat operasi tersebut tidak terlalu membosankan untuk ditulis.
zwol

4

Kesalahan dalam mencoba membuat perangkat veth disebabkan oleh perubahan cara ipmenafsirkan argumen baris perintah.

Doa yang benar ipuntuk membuat sepasang perangkat adalah

ip link add name veth0 type veth peer name veth1

( nameinstad dev)

Sekarang, bagaimana cara mendapatkan traffic dari namespace ke terowongan VPN? Karena Anda hanya memiliki perangkat tun yang Anda inginkan, "host" harus merutekan. Yaitu membuat pasangan veth dan menempatkan satu ke dalam namespace. Hubungkan yang lain melalui perutean ke terowongan. Dengan demikian, aktifkan penerusan, dan kemudian tambahkan rute yang diperlukan.

Sebagai contoh anggaplah itu eth0adalah antarmuka utama Anda, tun0adalah antarmuka terowongan VPN Anda, dan veth0/ veth1sepasang antarmuka yang veth1ada di namespace. Di dalam namespace Anda menambahkan hanya rute default untuk veth1.

Pada host yang Anda butuhkan untuk menggunakan routing kebijakan, lihat di sini misalnya. Apa yang kamu butuhkan:

Tambahkan / tambahkan entri seperti

1   vpn

untuk /etc/iproute2/rt_tables. Dengan ini, Anda dapat memanggil tabel (belum dibuat) dengan nama.

Kemudian gunakan pernyataan berikut:

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

Saya tidak dapat mencobanya di sini dengan pengaturan seperti milik Anda, tetapi ini harus melakukan apa yang Anda inginkan. Anda dapat menambahkannya dengan aturan filter paket sehingga vpn atau "guest" net tidak terganggu.

NB Pindah tun0ke namespace di tempat pertama sepertinya hal yang tepat untuk dilakukan. Tapi sepertimu, aku tidak berhasil. Perutean kebijakan sepertinya hal yang harus dilakukan selanjutnya. Solusi Mahendra berlaku jika Anda tahu jaringan di belakang VPN dan semua aplikasi lain tidak akan pernah mengakses jaringan itu. Tetapi kondisi awal Anda ("semua lalu lintas, dan hanya lalu lintas, ke / dari proses tertentu melewati VPN") terdengar seolah-olah yang terakhir tidak dapat dijamin.


Terima kasih, ini membuat saya sedikit lebih jauh, tetapi saya sekarang terjebak pada bagian "dan kemudian Anda menggunakan jembatan untuk menghubungkan perangkat veth ke terowongan" - silakan lihat pertanyaan yang direvisi.
zwol

Per jawaban yang baru saja saya posting, semuanya terjadi karena kesalahan konyol dalam skrip asli saya - "link scope" tidak berarti apa yang saya pikir artinya. Tetapi saya akan memberi Anda hadiah, karena Anda banyak bekerja untuk membantu saya mencoba berbagai kemungkinan, dan saya mungkin akan menyerah sama sekali jika Anda tidak.
zwol

Hai Zack, terima kasih banyak. Ruang nama dan perutean kebijakan adalah hal yang menarik untuk diteliti. Saya tidak akan melakukan banyak upaya dalam hal ini jika itu tidak menarik dengan sendirinya.
countermode

0

Jika jaringan yang Anda akses melalui VPN diketahui, Anda dapat mengedit tabel perutean untuk mencapai apa yang Anda inginkan.

  1. Perhatikan rute default Anda saat ini.

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

  2. Jalankan VPN dan ini akan memperkenalkan entri perutean.

  3. Hapus rute default saat ini (yang ditambahkan oleh VPN) di mana sebagai rute default sebelumnya menjadi entri default pertama dalam tabel.

    # 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. Tambahkan rute khusus ke jaringan yang ada di VPN untuk merutekan melalui tun0.

    # ip route add <net1>/16 dev tun0

    # ip route add <net2>/24 dev tun0

  5. Tambahkan kedua entri server nama (dalam resolv.conf) juga untuk VPN dan koneksi langsung.

Sekarang semua koneksi net1 dan net2 akan melalui VPN dan reset akan langsung (melalui wlo1 dalam contoh ini).


Sayangnya, jaringan yang diakses melalui VPN tidak diketahui sebelumnya, jadi ini tidak akan berfungsi untuk saya.
zwol
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.