Алексей Барабанов
Современный Linux-сервер: виртуализируем сетевые устройства
Часть 2
Воплотить схему виртуализации совершенно несложно. А дополнительно можно построить адаптивную систему коммутации.
В первой части статьи [1] были предложены пути и детально разобраны принципы искусственной виртуализации сетевых интерфейсов. Виртуализация не просто для усиления названа искусственной. Создание по специальному сетевому мосту на каждый используемый интерфейс не является самоцелью. Таким образом, достигаются следующие преимущества:
- Постоянство схемы разметки и наименования сетевых устройств.
- Независимость настройки сетевых сервисов от состояния реальных линий связи.
- Автоматизация сетевой коммутации.
- Удобство подключения виртуальных серверов.
Если просуммировать вышесказанное, то получится, что виртуализация позволяет создать независимую от состава оборудования, то есть стабильную сетевую разметку, на основе которой можно построить внутреннюю серверную архитектуру, обладающую устойчивостью к отказам и масштабируемостью. И самое главное, что такая архитектура может быть положена в основу многоцелевых и весьма разнообразных систем. Говоря иначе, таким путем будет разработан собственный серверный стандарт.
Но для начала формализуем процедуру виртуализации, так как без этого не получить корректного способа включения ее в стартовую последовательность Linux.
«Алгоритм» виртуализации
Вот как представляется последовательность действий по виртуализации согласно первой части статьи [1]:
- Переводим все устройства в новый именной ряд с помощью udev.
- Меняем их настройку так, чтобы обеспечить включение в состав сетевых мостов.
- Создаем необходимое число сетевых мостов с требуемыми настройками.
- Коммутируем реальные устройства с нужными сетевыми мостами.
После этого получаем виртуальный эквивалент старой настройки. Как подобное можно соединить со стандартными стартовыми скриптами?
Первый шаг
Отрабатывается при старте udev. Ничего менять не нужно, кроме некоторой правки имен устройств в /etc/udev/rules.d/30-net_persistent_names.rules. Как уже было предложено, назовем все реальные устройства как hweth*. Тогда строка, переименовывающая соответствующее устройство, например eth3, будет выглядеть следующим образом:
# grep eth3 /etc/udev/rules.d/30-net_persistent_names.rules
SUBSYSTEM=="net", ACTION=="add", SYSFS{address}=="00:14:85:21:1b:2f", IMPORT="/sbin/rename_netiface %k hweth3"
Подробнее о применяемой в настройках udev нотации вы можете прочесть в [2] и, конечно же, в документации, сопровождающей пакет udev. Здесь лишь поясню, что в приведенной строке задается переименование сетевого устройства с аппаратным адресом 00:14:85:21:1b:2f в процессе его добавления в систему. После выполнения данного правила в системе появится устройство не с тем именем, что выдается ядром в порядке очередности, а с требуемым, в нашем случае это будет hweth3 (от hardware ethernet). Теперь должно быть уже понятно, что если случайно назначаемое имя совпадет с одним из уже используемых, то скрипт rename_netiface не сможет выполнить свою задачу. И не важно, что данное совпадение может носить временный характер. Чтобы такого конфликта не возникало, используйте нестандартные имена. Нам такая рекомендация только на руку!
Второй шаг
Надо сделать все реальные устройства безадресными или, например, установить им адрес 0.0.0.0, чтобы можно было воспользоваться в настройке и управлении стандартными системными скриптами. Если на этапе установки системы пакетом YaST были уже настроены сетевые интерфейсы, то достаточно поправить им адрес, если же нет, то надо создавать их сразу с нулевым адресом (см. рис. 1).
Рисунок 1. Настройка безадресного физического интерфейса
Для уже описанного в предыдущем абзаце сетевого устройства файл параметров будет выглядеть так:
# cat /etc/sysconfig/network/ifcfg-eth-id-00:14:85:21:1b:2f
BOOTPROTO='static'
BROADCAST=''
IPADDR='0.0.0.0'
MTU=''
NAME='Giga-byte CK804 Ethernet Controller'
NETMASK='255.255.255.0'
NETWORK=''
REMOTE_IPADDR=''
STARTMODE='auto'
UNIQUE='DkES.QUKldky+OPE'
USERCONTROL='no'
_nm_name='bus-pci-0000:00:0a.0'
PREFIXLEN=''
Этот файл создавался с помощью YaST. Но можно его написать и самостоятельно. Более того, поскольку для всех интерфейсов эти файлы имеют практически одинаковый вид, отличаются лишь комментарием – оператор NAME – и уникальным номером конфигурации, который легко получить из начального, просто меняя в каждом последующем один или более символ на следующий в алфавитном порядке, то очень легко автоматизировать эту стадию настройки. Надо только придерживаться того правила, что имя файла формируется на основании физического адреса интерфейса по схеме ifcfg-eth-id-MAC.
Подготовленные подобным образом файлы настроек сетевых устройств будут полностью совместимы с существующей системой запуска сетевой подсистемы на сервере. И в процессе выполнения скрипта /etc/init.d/network все перечисленные интерфейсы будут «подняты» как безадресные и совершенно пассивные с точки зрения возможностей передачи трафика.
Третий шаг
Нам потребуется создать набор необходимых сетевых мостов. Это первый нестандартный шаг. Нужно сначала создать нужное число мостов с помощью команды «brctl addbr eth*». Вот тут мы будем использовать удобные и привычные имена сетевых интерфейсов, начинающихся с традиционного префикса «eth». То есть воспользоваться ими мы сможем лишь тогда, когда все реальные интерфейсы будут переименованы в иной именной ряд. Если в ходе работы будут добавляться или меняться сетевые карты, то эту процедуру надо будет повторять. Тогда имена на «eth» станут свободными, и их можно будет использовать в качестве имен сетевых мостов, которые, собственно, и послужат основой сетевой виртуализации. Как только мосты созданы, ими можно манипулировать с помощью стандартных сетевых скриптов. Для этого в директории /etc/sysconfig/network надо создать файлы с сетевыми настройками, названные по схеме ifcfg-eth*, где eth* соответствует имени сетевого моста, которому эти настройки предназначены. Например, некоторому сетевому мосту eth2, используемому как канал в Интернет, может соответствовать следующий файл настроек:
# cat /etc/sysconfig/network/ifcfg-eth2
BOOTPROTO='static'
UNIQUE='alZb.Tm92cpV9oKE'
IPADDR='213.xx.xx.xxx'
NETMASK='255.255.255.248'
BROADCAST='213.xx.xx.xxx'
NETWORK='213.xx.xx.xxx'
MTU=''
NAME='Virtual device eth2 VDSL'
STARTMODE='auto'
REMOTE_IPADDR=''
USERCONTROL='no'
PREFIXLEN=''
POST_UP_SCRIPT='/etc/fw/fw-on-internet'
POST_DOWN_SCRIPT='/etc/fw/fw-off-internet'
Перечень опций можно узнать из «man ifup». Здесь приведены реальные данные VDSL-соединения. Все опции очевидны и тривиальны, кроме двух последних строк, отвечающих за настройку сетевого экрана. Об этой теме поговорим особо чуть позже. Кстати, и для этой операции можно использовать YaST (см. рис. 2).
Рисунок 2. Создание виртуального устройства
Значение в выделенном на рис. 2 поле будет использовано при генерации имени такого виртуального устройства. В данном случае 0, то есть образуется eth0, и все параметры будут записаны в файл ifcfg-eth0.
Здесь уже становится понятным, что, хотя мы описываем последовательность настройки с номером 3, но в стартовой последовательности эти действия надо будет выполнить до старта скрипта /etc/init.d/network, иначе конфигурационные файлы виртуальных интерфейсов не будут обработаны и сетевые мосты не будут «подняты» и настроены нужным образом. Таким образом, реальный номер этого шага скорее будет «один с половиной» или «два без четверти», как угодно.
Четвертый шаг
Связывание реальных устройств с сетевыми мостами, не только можно выполнить в любое время после старта скрипта /etc/init.d/network, но и, самое приятное, что его можно независимо от всего остального повторять, меняя настройки. Как и в шаге 3, на данном этапе придется использовать нестандартный скрипт.
Итак, у нас вышло, что все необходимое можно реализовать двумя дополнительными скриптами после некоторой модификации существующих настроек. Рассмотрим подробнее.
Стартовая схема
Теперь давайте выделим из перечисленного ту часть, которую надо повторять при каждом старте, считая, что все необходимые шаги по настройке виртуализованного интерфейса уже выполнены однажды. В этом случае число шагов несколько сократится:
- Создадим необходимое число сетевых мостов, соответствующих используемым сетевым интерфейсам.
- Поднимем и реальные, и виртуальные интерфейсы.
- Скоммутируем реальные устройства с нужными сетевыми мостами.
Шаг 3 полностью соответствует стандартному скрипту /etc/init.d/network. Не забывайте перед стартом /etc/init.d/boot.udev проконтролировать, что в конфигурации udev не возникло нежелательных устройств, занимающих используемые системные имена eth*. Также необходимости предустановить настройки сетевого экрана. Обе задачи можно совместить, если скрипты, их выполняющие, вызвать из /etc/init.d/boot.local. Полностью стартовая последовательность должна выглядеть, как представлено на рис. 3.
Рисунок 3. Стартовая последовательность
В некоторой точке настройки системы (№1, рис. 3) выполняется скрипт boot.local, предназначенный для размещения дополнительных пользовательских функций, которые должны выполниться после системного старта. Это прекрасное место для инициализации сетевого экрана и контроля настроек udev. Сначала заблокируем весь трафик скриптом fw-init:
#!/bin/sh
iptables -F
iptables -t nat -F
iptables -t mangle -F
iptables -X
iptables -t nat -X
iptables -t mangle -X
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP
Затем, разрешим работу интерфейса lo скриптом fw-main:
#!/bin/sh
IPT=$(which iptables)
LIST1=("-A OUTPUT" "-A INPUT" "-A FORWARD" "-t nat -A POSTROUTING")
LIST2=("-o" "-i" "-i" "-o")
LIST3=("OUTPUT" "INPUT" "FORWARD" "POSTROUTING")
SIZE=4
IF=lo
I=0
while [ "$I" -lt "$SIZE" ] ; do
J=${LIST1[$I]}
K=${LIST2[$I]}
L=${LIST3[$I]}
$IPT $J $K $IF -j ACCEPT
$IPT $J -j LOG --log-prefix "main $L DROP " --log-level notice
$IPT $J -j DROP
I=$((I+1))
done
$IPT -P INPUT ACCEPT
$IPT -P OUTPUT ACCEPT
$IPT -P FORWARD ACCEPT
Этот скрипт поместит в каждую цепочку разрешение для трафика через lo, явное протоколирование и уничтожение всего остального трафика, и поменяет политики на разрешительные. Сохранение политики DROP не позволит с помощью анализа логов понять причину неправильной работы сети в случае ошибок в настройке сетевого экрана. А так, если уничтожение пакетов происходит явным образом, можно обнаружить, куда исчезает трафик. Скрипты являются частью системы управления сетевым экраном, которая здесь не описана. Разделение их на два независимых файла сделано для того, чтобы можно было каждой функцией воспользоваться по отдельности. Например, fw-init просто очищает все настройки iptables и блокирует трафик.
Ну и напоследок чрезвычайно простая программка fixudev, удаляющая все строки в настройках udev, изменяющие имена в те, что используются в схеме виртуализации:
#!/bin/sh
N=/etc/udev/rules.d/30-net_persistent_names.rules
T=/tmp/30-net_persistent_names.rules.$$
cat $N | grep -v "k eth" >$T
mv $T $N
Такой скрипт, конечно же, может испортить настройку нового сетевого интерфейса, произведенную через YaST, но он не позволит разрушить уже созданную сетевую настройку из-за конфликта имен.
В точке 2 (см. рис. 3) отработает boot.udev, который по стандартным зависимостям выполняется после boot.local, и настроит необходимое для работы службы udev.
И теперь в точке 3 (см. рис. 3) все готово для создания виртуальных устройств сетевых мостов. Этим будет заниматься специальный стартовый скрипт bridgeprep. Полный его текст, точно так же, как тексты всех используемых здесь программ, размещается в прилагаемом архиве [3]. Все стартовые скрипты SUSE пишутся согласно стандартному шаблону /etc/init.d/skeleton. Поэтому отмечу лишь актуальные и системно-зависимые части.
Во-первых, в самом начале создается управляющая секция согласно LSB:
### BEGIN INIT INFO
# Provides: bridgeprep
# Required-Start: boot.udev
# Required-Stop: boot.udev
# Default-Start: 2 3 5
# Default-Stop:
# Short-Description: Ethernet bridge
# Description: Start Bridge to allow configure
Bridge device
### END INIT INFO
Здесь задается специальный идентификатор – bridgeprep, который позволит ссылаться на этот скрипт из управляющих секций других скриптов, далее указывается очередность – после boot.udev, и уровни выполнения, в которые скрипт включается по умолчанию. Если скрипт с такой управляющей секцией установить командой «chkconfig bridgeprep on», то он будет включен в уровни 2, 3, 5 на любое свободное место очередности в стартовой последовательности после boot.udev, а в стоповой – до boot.udev.
Во-вторых, проверяется программное окружение и настраиваются внутренние переменные:
# Check for missing binaries
# (stale symlinks should not happen)
BRIDGE_BIN=/sbin/brctl
test -x $BRIDGE_BIN || ( echo "not found $BRIDGE_BIN" ; exit 5 )
# Check for existence of needed config file and read it
BRIDGE_CONFIG=/etc/sysconfig/bridge
test -r $BRIDGE_CONFIG || ( echo "not found $BRIDGE_CONFIG" ; exit 6 )
. $BRIDGE_CONFIG
Отсутствие brctl или собственных управляющих файлов скрипта приведет к аварийному завершению его работы. В этом же фрагменте производится чтение управляющего файла, который стандартно располагается в /etc/sysconfig c совершенно неоригинальным именем bridge и представляет собой просто список переменных с присваиваемыми им значениями, заданный в формате командной оболочки bash. Это тоже совершенно стандартное решение для подобных системных параметров, и их задание и модификацию можно производить как из консоли с помощью редактора, так и используя оснастку в YaST. Содержание файла /etc/sysconfig/bridge будем рассматривать синхронно с кодом, его использующим.
В-третьих, небольшая характерная для SUSE «косметическая добавка»:
# Shell functions sourced from /etc/rc.status:
. /etc/rc.status
# Reset status of this service
rc_reset
Это позволит отражать результат работы скрипта в протоколе так, как принято для скриптов SUSE.
Ну и, наконец, актуальная часть. Рассмотрим только стартовую ветку оператора case. Все остальное работает точно таким же образом. Вот часть кода, отвечающего за старт:
case "$1" in
start)
echo "Starting bridge "
for i in ${BRIDGE_LIST} ; do
$BRIDGE_BIN addbr $i
P="BRIDGE_FD_$i" ; P=${!P} ; [ "1$P" != "1" ] && $BRIDGE_BIN setfd $i $P
P="BRIDGE_HELLO_$i" ; P=${!P}
[ "1$P" != "1" ] && $BRIDGE_BIN sethello $i $P
P="BRIDGE_STP_$i" ; P=${!P} ; [ "1$P" != "1" ] && $BRIDGE_BIN stp $i $P
done
$BRIDGE_BIN show
echo -n
# Remember status and be verbose
rc_status -v
;;
Предполагаю, что вы знакомы с синтаксисом языка сценариев bash, если нет, то подробности можно почерпнуть в отличном руководстве [4]. В указанном программном фрагменте используются косвенные ссылки. Сначала в переменной BRIDGE_LIST задается список сетевых мостов, которые будут созданы, например, так:
BRIDGE_LIST="eth0 eth1 eth2 eth3"
Переменная $i «пробегает» в цикле все значения из этого списка и применяется для последовательного создания всех устройств в команде brctl addbr $i. Дополнительно для каждого сетевого моста можно задать несколько параметров в формате BRIDGE_парамет_имя, например, так:
BRIDGE_FD_eth0=0
BRIDGE_HELLO_eth0=0
BRIDGE_STP_eth0=off
Если параметр определен в файле конфигурации /etc/sysconfig/bridge, то производится его настройка для соответствующего виртуального устройства. Поскольку все наши сетевые мосты будут использоваться исключительно как средства виртуализации, то представленные выше значения для FD, HELLO и STP, а именно 0,0 и off, надо указать для каждого сетевого моста в обязательном порядке.
Как видите, все очень просто. Новый стартовый скрипт надо установить принятым в SUSE способом:
# cp bridgeprep /etc/init.d
# ln –s /etc/init.d/bridgeprep /sbin/rcbridgeprep
# chkconfig bridgeprep on
После выполнения данного скрипта в процессе старта системы будут созданы сетевые мосты, и их можно будет «поднять», как и другие сетевые устройства, с помощью стандартного скрипта network, на рис. 3 пункт 4. Но надо непременно указать, чтобы скрипт network выполнялся после скрипта bridgeprep. Для этого в дистрибутивном скрипте подправим одну строку в управляющей секции LSB:
# Required-Start: $local_fs bridgeprep
где укажем, что работа скрипта network теперь зависит еще и от bridgeprep. Таким образом, при очередной перенастройке местоположения network в стартовой последовательности «chkconfig network off : chkconfig network on», этот скрипт займет правильное положение согласно рис. 3.
Если до запуска network в основном происходит настройка локальных сервисов, то после network будут запущены все сетевые устройства независимо от состояния внешних линий, и можно смело запускать также и сетевые сервисы... – все равно ни один из них не будет доступен снаружи. Полностью сервер подключится к сети лишь после отработки скрипта bridgeup (пункт 5, рис. 3). Этот скрипт, отмеченный на рисунке голубым контуром, является нестандартным дополнением и построен аналогично рассмотренному ранее bridgeprep. Но у него уже иная стартовая зависимость:
# Required-Start: random bridgeprep network
И, конечно же, иной алгоритм работы. Этот скрипт производит сборку сетевых мостов, то есть подключение к ним физических устройств. Полностью его работу рассмотрим в следующем разделе, а сейчас приведу лишь фрагмент, производящий статическую сборку:
for i in ${BRIDGE_LIST} ; do
eval j=\$BRIDGE_DEVS_$i
if [ "1$j" != "1" ] ; then
# static device list
for k in $j ; do
[ "1$k" != "1" ] && {
$BRIDGE_BIN addif $i $k
BRIDGE_POOL=`remove_dev $k`
}
done
else
...
Здесь все очевидно – если надо включить некоторые устройства в нужный мост, то в /etc/sysconfig/bridge записывается:
BRIDGE_DEVS_eth0="hweth0 hweth2"
И два физических интерфейса с именем hweth0 и hweth2 будут добавлены командой brctl addif к мосту eth0. Этот скрипт тоже должен быть установлен тем же способом, что и предыдущий.
Подведем итоги. Цена всей доработки стартовой схемы, как и обещано, два нестандартных скрипта и небольшая правка в network. После этого все начинает функционировать по-новому, так, как планировалось в [1].
Отдельно опишу, как решаются проблемы с настройкой сетевых экранов. Итак, после boot.local во всех цепочках уничтожаются все пакеты кроме следующих через lo. И в промежутке от пункта 1 и вплоть до пункта 5 на рис. 3 можно делать с сетевым экраном что угодно. Это никак не отразится на работе сервера и его служб, если все реальные сетевые устройства настроены как 0.0.0.0, а все виртуальные еще не подключены ни к одному реальному. Поэтому предлагается настройки сетевого экрана выполнять для каждого интерфейса синхронно с его включением скриптом network, а точнее, программой ifup. Как уже было написано в начале, в каждый файл параметров сетевого интерфейса добавляются дополнительные настройки, например:
POST_UP_SCRIPT='/etc/fw/fw-on-internet'
POST_DOWN_SCRIPT='/etc/fw/fw-off-internet'
И соответствующие скрипты будут вызываться на выполнение сразу после поднятия интерфейса и после его отключения. Сам сетевой экран надо строить по схеме, аналогичной используемой для устройства lo:
- Создаются подцепочки для каждой основной цепочки iptables. Например inputeth0 для INPUT и устройства eth0.
- Затем они наполняются нужными правилами, завершаемыми правилом с целью DROP.
- И лишь после того в начало основной цепочки добавляется переход на созданный фильтр с квалификатором (опцией, фильтрующей трафик по принадлежности к интерфейсу).
Подобная схема полностью исключает возможности конфликтной или недостаточной фильтрации, так как за каждый интерфейс отвечает лишь одно правило в основной цепочке и собственная подцепочка фильтра, завершаемая правилом с целью DROP, то есть не теряющая правила ни в каком случае.
Два последних скрипта в стартовой последовательности (см. рис. 3), network и bridgeup, можно останавливать и запускать независимо от всего остального. Но, к сожалению, нет штатного способа указать, что перед остановкой network надо остановить bridgeup и, соответственно, после запуска network не забыть запустить и bridgeup. А так как здесь идет речь о минимальном вмешательстве в системные скрипты, то ограничимся тем, что просто запомним эту взаимосвязь.
Адаптивная коммутация
Теперь выполнены почти все обещания из первой части [1]. Но есть еще одно важное и пока не реализованное преимущество схемы виртуализации – соединение виртуальных устройств с реальными, согласно фактически произведенной внешней коммутации. Иначе говоря, в ходе выполнения bridgeup сервер должен проверить соответствие реальных подключений планируемым. Разберем это на конкретном примере. Безусловно, пример не идеален, так как он специфичен моей практике. Но описанные методы позволят построить вам собственную схему и для других условий работы сервера.
Далее все буду описывать согласно рис. 4. Итак, есть сервер с четырьмя сетевыми картами, названными hweth0-4. Есть три внешних кабельных подключения (розовые квадраты) – локальная сеть (LOCALNET), основной ISP, подключенный через ADSL, и дополнительное подключение к региональной сети (MAN ISP), где провайдер использует DHCP. Внутри сервера создаются тоже четыре сетевых интерфейса (голубые квадраты). Первый – для работы с локальной сетью (LOCALNET). Второй – как виртуальная внутренняя сеть для связи виртуальных же машин (CLUSTERNET). Третий – для соединения с ADSL-модемом (ADSL ISP). И четвертый для подключения к региональному провайдеру (MAN ISP).
Рисунок 4. Сетевая разметка тестового сервера
Задача заключается в том, что операционная система, стартующая на этом сервере, не «знает», как на самом деле произведена коммутация внешних соединений, но должна это «выяснить» «на лету» и, соответственно, настроить сетевые мосты. А иначе, спрашивается, зачем мы все это затевали?
Вся работа будет производиться в скрипте bridgeup в самом завершении стартовой последовательности (рис. 3). Все интерфейсы уже подняты и сетевые экраны уже настроены (красная пунктирная линия на рис. 4). И вот теперь вопрос – есть ли способ узнать, что «на кабеле» в таком состоянии, когда физические интерфейсы еще не включены в мосты?
Ну, конечно же, всем известная утилита tcpdump позволит «прослушать» такие интерфейсы. Если вызвать ее как «tcpdump -ln -i hweth0», то можно получить протокол всех «пойманных» там пакетов и по характеру трафика понять, какой провод воткнут в джек сетевой карты hweth0. Точно так же, как это можно сделать взглядом, можно и просчитать через grep по сохраненному протоколу работы этой утилиты за некоторое контрольное время. Например, если мы знаем, что в локальной сети у нас используется сетевой коммутатор, работающий по протоколу STP, то даже в «пустой» сети можно поймать периодически рассылаемые сообщения, содержащие в расшифровке tcpdump подстроку «802.1d config». Такая проверка реализована в скрипте check-tcpdump [3]. Скрипт, в свою очередь, вызывается из bridgeup сразу после приведенного выше фрагмента как альтернатива, вызываемая по условию else, если отсутствует строка статического задания сборки моста:
eval j=\$BRIDGE_TCPDUMP_$i
if [ "1$j" != "1" ] ; then
# check tcpdump
L=""
for k in ${BRIDGE_POOL} ; do
n=`${BRIDGE_CHK_TCPDUMP} $k "$j" | grep BINGO`
[ "1$n" != "1" ] && {
$BRIDGE_BIN addif $i $k
L="$L $k"
}
done
for k in $L ; do
BRIDGE_POOL=`remove_dev $k`
done
else
Как уже видно, за эту ветку настройки отвечает специальная строка конфигурации сетевого моста:
BRIDGE_TCPDUMP_eth0="802.1d config"
Если она определена, то скрипт перебирает все устройства, оставшиеся в списке незанятых (BRIDGE_POOL), и проверяет протокол перехваченного tcpdump трафика с помощью check-tcpdump на совпадение с заданным образцом. Безусловно, выбор правильного и эффективного образца для сравнения всецело на совести системного администратора, производящего настройку, так как бывают сети с чрезвычайно замусоренным трафиком. Например, данная строка поиска работала до тех пор, пока в сети MAN не был подключен аналогичный коммутатор, как и в локальной сети. Тогда пришлось заменить контрольный образец на более подробный «802.1d config 8000.00:11:d8:cb:df:99.8001», содержащий MAC-устройства. И именно по этой причине или из-за отсутствия трафика вообще, или из-за того, что нельзя гарантированно в течение времени прослушивания получить уверенный отклик, приходится прибегать к способам активного тестирования кабельных подключений.
Итак, если слушать нечего или невозможно, тогда остается «спросить» у сети что-то конкретное. Как принято выяснять наличие или отсутствие сетевых хостов – с помощью icmp. Но у нас еще нет скоммутированных соединений, и такое высокоуровневое средство не подходит, так как обмен посылками icmp является достаточно многоступенчатым процессом. Нам придется выбрать что-то попроще. Самое простое – arp-запрос. Только сформировать его надо будет не штатной утилитой, так как сервер еще реально не подключен к сетевым соединениям. Для таких целей прекрасно подходит утилита nemesis [5]. Можно взять готовый бинарный пакет [6] и установить его в систему. Arp-запрос позволит узнать о существовании в сетевой среде, подключенной к исследуемому интерфейсу, некоторого хоста, отвечающего по требуемому адресу. При этом запрос отправляется в стиле «скажите хосту с адресом1, какой физический адрес имеет хост с адресом2». Очень удобно то, что подобный запрос практически никогда не блокируется, так как протокол ARP отвечает за самое низкоуровневое взаимодействие. В терминах утилиты nemesis это выглядит так:
nemesis arp -d устройство -S адрес1 -D адрес2
Вызов nemesis добавляется между запуском tcpdump и отсчетом контрольного времени. После прекращения прослушивания надо точно также профильтровать результат расшифровки трафика tcpdump. Но только образец для поиска будет иной, а именно «arp reply адрес2 is-as». Все это выполняется скриптом check-arp [3].
Теперь для настройки надо лишь определить хост, наличие которого будет со всей определенностью указывать на нужную сетевую среду. Например, если сетевой мост eth2 должен быть подключен к модему ADSL с адресом 10.0.0.2 (рис. 4, зеленая стрелка), то в настройку добавляем строку:
BRIDGE_ARP_eth2="10.0.0.1 10.0.0.2"
Очередной фрагмент кода bridgeup, отвечающий за следующую альтернативу else, вызываемую в отсутствие других настроек, проверит существование переменной BRIDGE_ARP-устройство и произведет соответствующую проверку оставшихся в списке BRIDGE_POOL-устройств в поиске подходящего:
eval j=\$BRIDGE_ARP_$i
if [ "1$j" != "1" ] ; then
# check arp
L=""
for k in ${BRIDGE_POOL} ; do
n=`${BRIDGE_CHK_ARP} $k $j | grep BINGO`
[ "1$n" != "1" ] && {
$BRIDGE_BIN addif $i $k
L="$L $k"
}
done
for k in $L ; do
BRIDGE_POOL=`remove_dev $k`
done
fi
fi
fi
В нашем случае других проверок не предусматривается и приведенный ваше блок закрывает все операторы if, но пытливый и изобретательный читатель может продолжить настройки, если так будет требоваться конкретной задачей.
Вот как это действует на практике. После старта системы, если кабельные подключения соединены согласно рис. 4, то запрос статуса соединения виртуальных устройств покажет следующее (листинг приведен с сокращениями):
# rcbridgeup status
Checking for service bridge running
bridge name bridge id STP enabled interfaces
eth0 8000.000479661b70 no hweth1
eth1 8000.000000000000 no
eth2 8000.001022ff43d7 no hweth0
eth3 8000.00xxxxxxxxxx no hweth3
eth0 Link encap:Ethernet HWaddr 00:04:79:66:1B:70
inet addr:192.168.0.9 Bcast:192.168.0.255 Mask:255.255.255.0
...
eth1 Link encap:Ethernet HWaddr 00:00:00:00:00:00
inet addr:192.168.254.1 Bcast:192.168.254.255 Mask:255.255.255.0
...
eth2 Link encap:Ethernet HWaddr 00:10:22:FF:43:D7
inet addr:10.0.0.1 Bcast:10.0.0.255 Mask:255.255.255.0
...
eth3 Link encap:Ethernet HWaddr 00:XX:XX:XX:XX:XX
inet addr:172.16.YY.YY Bcast:172.16.255.255 Mask:255.255.0.0
...
|
Интерфейс eth3 уже успел получить адрес через DHCP (и адрес, и MAC скрыты, так как их можно легко использовать для спуфинга в MAN). Затем изменяем коммутацию случайным образом. Чтобы система «почувствовала» это изменение, в нашем случае надо перезапустить bridgeup вручную, поскольку нам достаточно правильно настроить сеть после включения устройства. Но можно настроить периодический контроль, если персонал в серверной имеет привычку к спонтанной перекоммутации кроссов.
# rcbridgeup status
Disassemble bridgies
bridge name bridge id STP enabled interfaces
eth0 8000.000000000000 no
eth1 8000.000000000000 no
eth2 8000.000000000000 no
eth3 8000.000000000000 no
done
|
# rcbridgeup start
Assemble bridgies eth0 eth1 eth2 eth3
Hardware pool hweth0 hweth1 hweth2 hweth3
bridge name bridge id STP enabled interfaces
eth0 8000.00xxxxxxxxxx no hweth3
eth1 8000.000000000000 no
eth2 8000.0011d8956c2e no hweth2
eth3 8000.000000000000 no
Free NICs hweth0 hweth1
done
|
Процесс отключения происходит совершенно безупречно, а вот при подключении не обнаруживается соединение с MAN, так как в используемой сети применяется контроль по MAC, а разрешенное к применению устройство hweth3 было уже задействовано для связи с локальной сетью. И теперь уже две сетевые карты остались незадействованными. Таким образом, система предотвратила недопустимое соединение.
После того как только кабели снова были возвращены на старые места и перезапущен bridgeup, все пришло в норму:
# rcbridgeup stop
# rcbridgeup
Assemble bridgies eth0 eth1 eth2 eth3
Hardware pool hweth0 hweth1 hweth2 hweth3
bridge name bridge id STP enabled interfaces
eth0 8000.000479661b70 no hweth1
eth1 8000.000000000000 no
eth2 8000.001022ff43d7 no hweth0
eth3 8000.00xxxxxxxxxx no hweth3
Free NICs hweth2
done
|
Заключение
Вот и все – запланированные цели достигнуты. Но полученное несколько больше простого технического приема. Таким образом создается независимая сетевая архитектура сервера. Или, говоря иначе, создается дополнительный слой виртуализации, что позволяет унифицировать так же и все настройки, основанные на сетевых параметрах, для систем, расположенных выше данного слоя. Максимальный выигрыш получается при использовании виртуальных серверов. Так как миграция, а также и создание, и резервирование, и восстановление их требует единства внутрисервернной среды. Подробнее тема сетевой архитектуры будет рассмотрена в статьях, посвященных виртуальным серверам.
Несколько неожиданным может показаться еще один вывод. Описанное адаптивное поведение сетевой системы по отношению к внешней коммутации, кроме простого сохранения правильных настроек сетевого экрана, может привести и к философским выводам. Если считать линии коммуникации для компьютера эквивалентом органов осязания, то управление этими органами является необходимым минимумом интеллектуальности. Если бы сетевые розетки были снабжены манипуляторами, ничего бы не удивляло. Но здесь получается, что и без механического приспособления компьютер под управлением Linux может «сам» выбрать правильное подключение. Иначе говоря, может скомпенсировать ошибку оператора или техника. То есть мы сделали компьютер немного умнее, «научив» его «видеть» и менять интерфейсное подключение программным способом.
- Барабанов А. Современный Linux-сервер: виртуализируем сетевые устройства. //Системный администратор, №6, 2006 г. – С. 10-17. – http://www.samag.ru/cgi-bin/go.pl?q=articles;n=06.2006;a=01.
- Daniel Drake. Writing udev rules – http://www.reactivated.net/writing_udev_rules.html.
- Архив исходных текстов к статье – http://www.barabanov.ru/arts/modernserver/netsrc.tgz.
- Advanced Bash-Scripting Guide. Искусство программирования на языке сценариев командной оболочки. Версия 2.5 (15 февраля 2004 г.). Автор: Mendel Cooper (thegrendel at theriver dot com). Перевод: Андрей Киселев (kis_an at mail dot ru) – http://gazette.linux.ru.net/rus/articles/abs-guide/index.html.
- Nemesis packet injection utility – http://www.packetfactory.net/projects/nemesis.
- Бинарная сборка nemesis для SUSE – http://www.barabanov.ru/arts/modernserver/nemesis-1.4beta3-1ab.i586.rpm.