Подавайте весь трафік через OpenVPN лише для певного простору мереж імен


16

Я намагаюся налаштувати VPN (використовуючи OpenVPN) таким, щоб весь трафік, і лише трафік, до / з конкретних процесів проходив через VPN; інші процеси повинні продовжувати безпосередньо використовувати фізичний пристрій. Наскільки я розумію, спосіб цього зробити в Linux - це мережеві простори імен.

Якщо я нормально використовую OpenVPN (тобто перенаправляю весь трафік від клієнта через VPN), він працює чудово. Зокрема, я запускаю OpenVPN так:

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

(Відредагована версія destina.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. За припущенням, що " ви можете [все-таки] призначати лише віртуальний інтерфейс Ethernet (veth) в мережевий простір імен " (який, якщо це правда, приймає цьогорічну нагороду за найсмішніше непотрібне обмеження API), створюючи veth пару та міст, і поклавши один кінець вет-пари в область імен. Це навіть не доходить до того, що скинути рух на підлогу: це не дозволить мені поставити тунель у міст! [EDIT: Це, мабуть, тому, що в мости можна вводити тільки пристрої з краном . На відміну від неможливості розмістити довільні пристрої в мережевому просторі імен, це насправді має сенс, що з мостами є концепцією рівня Ethernet; на жаль, мій постачальник 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

призначення.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 не мають ядра, встановленого в розділі ОС, тому я не впевнений, що міг би завантажити завантажений модуль.
zwol

Чи lsmodвзагалі дає якийсь результат? Чи є каталог /lib/modules?
контрмод

lsmod: command not found. Є /lib/modules, але в ньому немає ніяких модулів, це лише купа каталогів на одне ядро, що містять порожні modules.depфайли. Я загляну в допомогу, що стосується Linode, і з’ясую, чи так це має бути.
zwol

хм ... дуже дивно. Я не знайомий з Linode, але для мене це виглядає так, ніби пристрої veth не підтримуються.
контрмоде

Відповіді:


9

Ви можете запустити посилання OpenVPN всередині простору імен, а потім запустити кожну команду, яку ви хочете використовувати для цього посилання OpenVPN всередині простору імен. Деталі, як це зробити (не моя робота) тут:

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

Я спробував це, і це працює; ідея полягає у створенні користувацького сценарію для виконання фаз до і підключення маршруту OpenVPN-з'єднання всередині певного простору імен замість глобального. Я цитую з вищенаведеного посилання на випадок, якщо в майбутньому воно перейде в офлайн:

Спочатку створіть сценарій --up для OpenVPN. Цей сценарій створить інтерфейс тунелю 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 і скажіть йому використовувати наш --up скрипт замість виконання ifconfig та route.

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

Тепер ви можете запустити програми, які потрібно тунелювати так:

ip netns exec vpn command

Єдина уловка полягає в тому, що вам потрібно мати root для виклику, ip netns exec ...і, можливо, ви не хочете, щоб ваша програма запускалася як root. Рішення просте:

команда sudo ip netns exec vpn команда sudo -u $ (whoami)

1
Привіт і ласкаво просимо на сайт! Ми рекомендуємо користувачам хоча б узагальнити (по можливості) вміст посилань, які вони вставляють у відповіді. Це допомагає зберегти якість відповідей у ​​випадку, коли посилання стає нестабільною (наприклад, сайт більше не доступний). Будь ласка, вдосконаліть свою відповідь, включивши найважливіші частини / інструкції із пов’язаної статті.
Ератьєль

Це чудово, але вам потрібно поставити одиничні лапки навколо відкриваючого роздільника heredoc, щоб оболонка не розширила всі змінні.
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 не помилився. І без "посилання на обсяг" працює і мій оригінальний сценарій.

(Як я дізнався це, запитаєте ви? Запустивши straceпроцес openvpn, встановіть hexdump все, що він читає, з дескриптора тунелю, а потім вручну розшифруйте заголовки пакетів.)


Будь-який шанс ви можете написати керівництво по цьому? Я намагаюся створити щось подібне, але важко сказати, з яких частин вашого питання добре починати і які шляхи, які призвели до невдачі.
тремтіти

@tremby Я, швидше за все, не встигну це зробити найближчим часом, але ви можете виявити корисним github.com/zackw/tbbscraper/blob/master/scripts/openvpn-netns.c .
zwol

Так, я не впевнений, що програма С на 1100 рядків допоможе. Як щодо лише остаточного конфігурації, сценаріїв і закликів, які виконали роботу за вас? ... Або це програма C остаточна реалізація цього?
тремті

@tremby Так, програма C - це моя остаточна реалізація. (У моєму сценарії використання він має бути налаштований, розумієте.) Можливо, ви зможете просто відмовитись від цього - якщо великий коментар вгорі не пояснює, як ним користуватися, дайте мені знати.
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

( nameзамість dev)

Тепер, як вивести трафік з простору імен до тунелю VPN? Оскільки у вашому розпорядженні є лише пристрої настройки, "хост" повинен пройти маршрут. Тобто створити veth пару і ввести її в простір імен. Підключіть іншого за допомогою маршрутизації до тунелю. Таким чином, увімкніть переадресацію, а потім додайте необхідні маршрути.

Для прикладу припустимо, що 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, ні "гостьова" мережа не турбуються.

Примітка. Перехід tun0в область імен в першу чергу схоже на те, що потрібно зробити. Але, як ти, я не працював над цим. Маршрутизація політики виглядає як наступна правильна річ. Рішення Mahendra застосовне, якщо ви знаєте, що мережі, що стоять за VPN, і всі інші програми ніколи не матимуть доступу до цих мереж. Але ваша початкова умова ("весь трафік, і лише трафік до / з конкретних процесів проходить через VPN") звучить так, ніби останній не може бути гарантований.


Дякую, це додає мене трохи далі, але я зараз застряг на частині ", а потім ви використовуєте міст для підключення пристрою вентилятора до тунелю", - дивіться переглянутий питання.
zwol

На відповідь, яку я щойно опублікував, вся річ зводиться до дурної помилки в моєму оригінальному сценарії - "посилання на область дії" не означає, що я вважав, що це означає. Але я збираюся дати тобі нагороду, тому що ти доклав багато роботи до того, щоб допомогти мені випробувати різні можливості, і я, мабуть, відмовився б взагалі, якби ти цього не зробив.
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. Додайте обидва записи сервера імен (у резоль.conf), а також для VPN та прямого з'єднання.

Тепер усі з'єднання net1 та net2 пройдуть через VPN, а скидання піде безпосередньо (через wlo1 у цьому прикладі).


На жаль, мережі, до яких звертаються через VPN, не відомі заздалегідь, тому це не спрацює для мене.
zwol
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.