Жили 20 лет не тужили, имея подключение к провайдеру по pppoe, сетку/24. Филиалы подключали ко внутренней сети через туннели, благо трафик по области был безлимитный, а в Интернет безлимитным был только трафик центрального офиса. Маршрутизатор был нами бережно настроен как центральный брандмауэр на входе, все маршруты замыкались на нём, то есть на остальных хостах можно было ограничиться маршрутом по умолчанию и не беспокоиться о проникновении извне.
По независящим от нас и не касающимся данной статьи причинам, с нового года поменялся провайдер. Выдал он нам небольшую сетку, сказал адрес маршрутизатора по умолчанию, находящийся в этой же сети. В общем, ситуация вполне стандартная. Удивительно, что при такой ситуации в сети не разбираются проблемы такого подключения.
А проблемы сразу заметны, ведь маршрутизатор не под нашим управлением:
Данные проблемы можно попытаться решить наскоком, и что может ложно успокоить, "всё будет работать". Поменяв маршрут по умолчанию на белых хостах на собственный маршрутизатор создастся видимость, что всё починилось: маршрутизация до филиалов и серых сетей появилась, а брандмауэр даже сможет срабатывать на выход. Но кому более всего нужен брандмауэр на выход? Вот то-то же. Да и с маршрутизацией не всё на самом деле в порядке.
На каждый посланный вами из белой сети в Интернет пакет, вы будете получать ICMP redirect, говорящий, что маршрутизатор вами установлен не тот:
PING branch.mydomain.ru (branch-IP): 56 data bytes From GW-IP Redirect (change route) 84 bytes from branch-IP: icmp_seq=0 ttl=63 time=N ms
Windows машины, у которых по умолчанию применяется политика реагирования на этот код, будут бесконечно добавлять в таблицу маршрутизации записи на каждый хост в Интернете через маршрутизатор провайдера.
Хорошо, а давайте добавим все статические маршруты, а между провайдерским и своим коммутатором поставим прозрачный файервол. Ну что ж, это решение будет работать пока вам не надоест возиться с маршрутизацией и не возникнет мысль, что может сделаем управляющий интерфейс прозрачного файервола в белой сети и он и будет центральным маршрутизатором. Увы, как только вы это сделаете, всё вернётся к проблеме, описанной в начале главы.
Из предыдущей главы стало понятно, что нам нужно:
Как ни странно, но в сети можно найти решение как это сделать, но это решение предлагается как совершенно странный нетипичный пример сети с запутанными условиями "из реальной жизни", совсем не похожей на рассматриваемый пример типичного подключения.
Как это делается в других ОС, отличных от Linux оставим на самостоятельный поиск читателю, а для Linux эта строчка выглядит вот так и немного странно (тсс, для русских это будет ещё более забавным, но пусть это будет маленьким русским секретом):
ebtables -t broute -A BROUTING -i eth-in -p ipv4 -j redirect --redirect-target DROP
Где eth-in — входящий интерфейс со стороны вашей остальной сети.
Тот, кто уже имел дела с конфигурацией файервола на Linux, взглянув на эту строчку скажет, что мы запретили совсем необходимый переброс пакета со входящего интерфейса на выход. Да, именно так. Дело в том, что ebtables имеет много точек разветвления, запрещая одно ветвление, мы заставляем перемещать пакет в другое, если политика по умолчанию или следующие правила это не запрещают. В данном случае пакет покинет L2-уровень, за который ответсвеннен ebtables и мост, а пойдёт на L3-уровень в маршрутизацию и iptables. Посмотрите на примеры использования ebtables, вы там в большинстве случаев найдёте именно правила с DROP. Такова уж судьба у L2-уровня, другие действия нужны к ну уж очень занятным и нетипичным конфигурациям, типа кластеров и др.
Отлаживание вживьём файервола и эксперименты с L2-уровнем зачастую приводит к получению огромного недовольства пользователей и быстрых полурешений. Давайте соорудим тестовую лабу, благо на современных компьютерах запустить многочисленные хосты со связями и потребляемыми ресурсами вовсе не трудно и не накладно.
Упростим схему сети до следующей:
Помимо вышеуказанных функций, на brouter также возложим обязанности NAT для пользователей серой сети центрального офиса, а NAT для филиала в Интернет будет делать маршрутизатор филиала. О том, что у нас прозрачный файервол намекает отсутствие IP на одном из интерфейсов brouter. Хоть и он и прозрачный мост, но частично, потому для наглядности будем считать, что управляющий интерфейс с IP смотрит вовнутрь, но на самом деле из-за решения с белым адресом, уверяю вас, это совершенно безразлично, куда будет смотреть интерфейс с IP, он же будет назначен самому мосту, а картинка отражает только желание общего перфекционизма.
Так как нам необходимо разобраться ещё и с уровнем L2, да и для упрощения конфигурации соединений виртуальных хостов, нарисуем ту же картинку на уровне L2. Сразу сделаем небольшое усложнение. Пусть три интерфейса — это много, попробуем заодно сделать VLAN-ы, сократив один интерфейс у brouter.
Одна из сетей, на картинке — красная, будет подсоединена через нетегированный, но нетранковый интерфейс к brouter-у, белая сеть будет на тегиррованом VLAN-е, а к bouter-у будет идти транк. Тут нас ждёт большое разочарование. Встроенный в ядро мост совсем не понимает VLAN-ы никак, это очень простой мост, полноценный управляемый коммутатор можно сделать на OpenSwitch/vSwitch, но увидев его многомегабайтный размер как исходного кода так и документации, я отложил разбирательство с ними на потом. Как следствие, нам придётся положиться на то, что все хосты, кроме провайдерского под нашим полным контролем, подавать на самом деле на интерфейсы хостов внутри нашей сети вокруг brouter-а, а именно на www и user мы будем транки, разбивать же на access vlan будут они честно-пречестно сами. Хочется подчеркнуть, что это проблема только тестовой лабы, при переносе на железо, полноценный коммутатор решит полностью проблему разграничения VLAN.
Итак, нам понадобится 6 хостов, 4 моста, ещё один мост будет в brouter-е, 9 интерфейсов для связи с виртуальными машинами (tap). Не такая уж и маленькая лаборатория.
В отличии от других ОС, Linux с point-to-point интерфейсами очень универсален и своеобразен, взяв самое лучшее от других ОС. Многие даже не догадываются, что настоящих ptp интерфейсов в Linux нет! Кто-то наверное помнит, что у ppp-интерфейса когда-то было можно было указать произвольную маску. Так вот, это можно сделать и сейчас у любого ptp-интерфейса. Зачем это надо? А вот для туннелей! Эта маска на самом деле половинчатая, на локальном адресе туннеля интерфейс ведет себя честным образом как ptp. Этот адрес в маршрутизации не участвует, его можно назначить, а это очень удобно, (в терминологии cisco — unnumbered) либо из существующей сети, озаботившись proxy-arp, либо даже из любого имеющегося адреса на хосте. А маска работает для удаленной стороны. Тем самым для туннелирования одной сети нам не понадобятся ни дополнительные "левые" сети, ни скрипты для манипулирования маршрутизацией, которые по своей сути не могут не иметь названия "из соплей и палок" в связи с некоторыми лагами по реагированию на события: интерфейс поднялся и тут же упал. Локальный внешний адрес туннеля будет адресом внешнего эзернет-интерфейса, удалённый внешний адрес — внешним адресом второго конца туннеля (это cisco делать может), а внутренние адреса будут соответсвенно: локальный — адресом внутреннего эзернет-интерфейса, удалённый — внутренним адресом второго конца туннеля, маска — маске туннелируемой удаленной сети. Вот это cisco делать не умеет!
Для удобства работы не с IP, а с именами, пропишем уж точно без вопросов единый файл /etc/hosts. Тут же станет понятно, что про адреса то мы и забыли. Пусть будет такая адресация, которую мы сразу же запишем в /etc/networks:
loopback 127.0.0.0 user-net 192.168.0.0 mydomain 192.168.1.0 internet 192.168.2.0 branch-net 192.168.3.0
Теперь уже можно таки взяться за /etc/hosts.
127.0.0.1 localhost 192.168.0.2 user.mydomain.ru user tap1 192.168.0.5 brouter-user.mydomain.ru brouter-user 192.168.1.1 www.mydomain.ru www tap2 192.168.1.5 brouter.mydomain.ru brouter tap3 0.0.0.0 zero tap4 192.168.1.6 gw.provider.ru gw tap5 192.168.2.1 uplink.provider.ru uplink tap6 192.168.2.3 branch.mydomain.ru branch tap7 192.168.3.1 branch-gw.mydomain.ru branch-gw tap8 192.168.3.2 branch-host.mydomain.ru branch-host tap9
Зачем в hosts прописаны tap-ы, ведь на виртуальных машинах будут eth-ы? А так удобнее получать имя хоста скриптом, ведь eth-ы будут только eth0 и eth1, а так же vlan-овский eth0.2, а приписав последнюю цифру mac, мы можем искать имя хоста по имени получившегося tapN.
ebtables для своей работы требует /etc/ethertypes, возьмите его со своей полноценной linux-системы.
Некоторые сетевые серверы и клиенты, запускаемые для проверки работы брандмауэра (telnet, ftp) просят /etc/protocols, также возмите его со своей полноценной linux-системы.
sshd требует /etc/shells, так как в нашем дистрибутиве кроме ash (и линка на sh) других shell-ов нет, то и поместите эти две строчки /bin/sh и /bin/ash в /etc/shells.
Если вышеуказанные файлы стандартны для unix-систем, то дальше пойдут уже нестандартные по настройке специфично для нашей лабы.
Для определения, какой маршрут по умолчанию должен установить конкретный хост, создадим конфиг-файл такого формата:
tap1 brouter-user tap2 brouter tap3 gw zero tap5 zero uplink tap7 uplink branch-gw tap9 branch-gw
Где, первое слово — номер первого интерфейса; второе слово — маршрутизатор по умолчанию; необязательное третье слово указывает, что хост имеет два эзернет-интерфейса.
Скрипт выключения redirect облагородим условием для лабы — выключить или включить.
#!/bin/sh ebtables -t broute -F if [ "x$1" != x ]; then ebtables -t broute -A BROUTING -i $1 -p ipv4 -j redirect --redirect-target DROP echo Disable brouting else echo Enable brouting fi
Параметр — входной интерфейс белой сети со стороны наших серверов. Главное тут не запутаться, выключение redirect есть главное предназначение скрипта, а включение brouting приводит к проблеме получения icmp redirect-а при указании шлюза по умолчанию не провайдера, а своего, либо к невозможности доступа к центральной маршрутизации при указании маршрутизатора провайдера.
Следующие конфиги будут всё более объёмными. Вот скрипт поднятия туннелей.
#!/bin/sh resolv() { grep -v '^#' /etc/hosts | grep -e "$1\." -e "$1\$" -e "$1 " | awk '{ print $1 }' } # for brouter, for branch need swap # ip utilite unsupport hostnames, need resolv to IP LOCAL=`resolv brouter` REMOTE=`resolv branch` BRT_LOCAL=brouter-user BRT_REMOTE=branch-gw NET_LOCAL=user-net NET_REMOTE=branch-net swap() { local __make_name1 local __make_name2 local __tmp1 local __tmp2 __make_name1=$1 __make_name2=$2 __tmp1="__tmp1=\$$__make_name1" eval $__tmp1 __tmp2="$__make_name1=\$$__make_name2" eval $__tmp2 __tmp2="$__make_name2=$__tmp1" eval $__tmp2 } if [ "x$1" = xbranch ]; then swap LOCAL REMOTE swap BRT_LOCAL BRT_REMOTE swap NET_LOCAL NET_REMOTE elif [ "x$1" != xbrouter ]; then exit 1 fi echo Configure IPIP tunnel: $NET_LOCAL - $NET_REMOTE ip tunnel add brt mode ipip remote $REMOTE local $LOCAL ttl 64 seq || exit 1 ifconfig brt $BRT_LOCAL pointopoint $BRT_REMOTE netmask 255.255.255.0 || exit 1
Без любимой присказки лектора тут не обойтись. "Как мы ясно видим из вышеуказанного", туннель будет поднимать IPIP, скрипт полууниверсальный, работает как для brouter-а, так и для branch, но эти имена, как и имена сетей к сожалению захаркодены внутри. Ещё одно разочарование: ip утилита не понимает имена хостов, только IP, так что из палок в скрипте сооружен резольвер. Общий разбор функции swap описан в трюках, более подробный выходит за рамки этой статьи. О маске у ptp интерфейса также можно обратиться к главе о трюках.
Конфиг брандмауэра разбит на два, первый — для входящих, второй — для проходящих пакетов и NAT, оба полууниверсальные. Входящий используется для brouter, gw и branch. Второй — для brouter и branch. gw хост, как хороший правильный провайдер, не должен фильтровать проходящие пакеты, а на филиал необходимо ставить свой брандмауэр. Полный разбор этих конфигов также выходит за рамки этой статьи. Впереди ещё много другого интересного!
Кто сталкивался с эмуляцией Linux, возможно уже знает как создать мини-дистрибутив для тестов. Но я с самого начала планировал посвятить большой объём этой статьи именно этому моменту — функционирования виртуальных хостов на моём варианте микро-Linux-ов под qemu.
Конфигурация хостового ядра имеет только два специальных условия: для запуска эмуляции нам потребуется поддержка kvm с поддержкой kvm-intel или kvm-amd от типа процессора, если он у вас, конечно, x86(-64). Для создания сетевой инфраструктуры на хосте вам понадобятся поддержка мостов и tap-интерфейсов. К сожалению манипуляция этим возможна только от суперпользователя или другими трюками, например с sudo, suid или из стартовых скриптов. Можно возразить, что сделав bridge-helper с эмуляцией suid через CAP, суперпользователь больше не потребуется, но в таком случае у нас пропадёт возможность определять какая машина работает, так как mac будет генерироваться helper-ом случайно динамически.
Для функционирования гостевых хостов на моём варианте микро-Linux-ов под qemu нам потребуется:
Безусловно, можно не использовать специфичные устройства, но виртуальные устройства обладают полезными свойствами по сравнению с эмулированными реальными: они быстрые, маленькие и ядру заранее известно, что виртуально-эмулируемые, потому не содержат каких-либо задержек для синхронизации с реальным железом и просты.
Несколько неочевидных замечаний. Если вы собираетесь работать только с блочным устройством virtio-blk, то ни IDE, ни SCSI ни SATA драйвера вам в ядре не понадобятся, хотя qemu их и предоставляет даже когда его не просят. Но загрузка с vda наверняка проблематична, например lilo, которое есть у меня работать с ним отказывается. Но это и не страшно, так как в нашей лабе будет совсем микро-Linux, не выходящий за рамки initrd, который, как известно загружается до защищенного режима через BIOS. В мой конфиг ядра я постарался включить все устройства предоставляемые qemu, мало ли для каких ещё целей понадобится сделать другую лабу. Получилось 2.4Mb, не мало, но, согласитесь, терпимо, ведь это без дополнительных модулей, всё что потребуется уже в монолите.
Если наше ядро 2.4Mb, сжатый initrd у меня получился менее 1Mb, то, с учётом копии предыдущего initrd, нам вполне хватит 6Mb диска для экспериментов. Qemu работает с разными форматами диска, но при таких небольших объемах вполне удобным для модификации будет и обычный raw-образ. Примерный скрипт для его создания:
#!/bin/sh . ./lab0.sh mkdir -p "$MOUT_POINT" "$MOUT_POINT2" chown $USER "$MOUT_POINT" "$MOUT_POINT2" # make 6Mb raw disk dd if=/dev/zero of=$IMG bs=1k count=6144 # make FAT, full disk, non floppy mode mkfs.fat -M 0xFF $IMG # install syslinux syslinux --install $IMG # mount raw disk, need fat/vfat support into kernel mount -o loop $IMG "$MOUT_POINT" # copy syslinux config file, kernel and ramdisk cp syslinux.cfg vmlinuz initrd.gz "$MOUT_POINT" umount "$MOUT_POINT" chown $USER $IMG
Где lab0.sh — общий для скриптов файл с параметрами:
#!/bin/sh # image name IMG=hdtest # non root user for tests usage USER=dzo MOUT_POINT=`pwd`/mnt2 MOUT_POINT2=`pwd`/mnt3
Обратите внимание, что диск не разбит на разделы. Для Linux это не требуется, а монтировать образ гораздо удобнее без смещения на нужный раздел в образе диска. syslinux-у — загрузчику с fat-файловой системы тоже не требуются разделы, да собственно это он делать и не умеет, так как работает только с активного раздела либо с дискетами, в том числе и эмулируемыми при загрузке с CD/DWD. Lilo нам не подойдёт, так как он изнутри гостя откажется понимать vda, а из главного хоста просто так уже ядро или образ initrd уже не поменяешь, с более умными загрузчиками тут нет смысла возиться. syslinux ставится ровно один раз на образ целого диска, а файлы ядра и initrd можно потом смело менять, не забывая отмонтировать образ диска, чтобы не порушить файловую систему на образе.
При экспериментах возможно вы захотите поменять файлы изнутри виртуальной машины, чтобы они сохранились в новом образе initrd. Как уже упоминалось в прошлой главе, это делает скрипт /etc/mkres:
#!/bin/sh dd if=/dev/zero of=/dev/ram1 bs=1k count=4096 mkfs.minix /dev/ram1 4096 mount -t minix /dev/ram1 /mnt cd / tar cf - bin etc home sbin usr var | ( cd /mnt; tar xf - ) cd /mnt mkdir dev dos proc sys mnt tmp opt chmod 1777 tmp rm -rf var/sheerdns var/run/* cd /etc umount /mnt mount /dos cd /dos mv initrd.gz init-old.gz gzip -9 < /dev/ram1 > initrd.gz freeramdisk /dev/ram1 echo "New initrd.gz placed to /dos/initrd.gz now" cd / umount /dos if [ x"$1" = x-r -o x"$1" = x-h ]; then shutdown $1 now fi
Создаем пустой ramdisk, копируем всё без каталогов, в которых могут скопиться логи и временные файлы, размонтируем, подключаем образ диска с которого грузимся с системой fat, сжимаем новый ramdisk, сохраняем старый, кладём новый, освобождаем ramdisk, если был вызов с ключом, то осуществляем shutdown с этим ключом. Объём несжатого ramdisk — 4МБ. Этот параметр надо согласовать с конфигом ядра, в ядре должно быть установлено не менее.
Ну вот, наконец, мы и подошли к тому моменту, к которому я шёл более 10 лет. Как многие уже знают, в небольших Linux-системах (рутеры, инсталяторы и пр.) в качестве основного исполняемого файла используется busybox. Выигрыш по объёму достигается как и урезанными функциями апплетов из busybox, которые заменяют многочисленные стандартные системные утилиты, так и переиспользованием общих функций, а также статической линковкой только тех функций из библиотек, которые используются только этой одной программой. Это всё замечательно, но как быть, если необходимы ещё программы, не включённые в busybox? Увы, при такой ситуации вполне возможно, что выигрыша в объёме при применения busybox-а не получится. Но есть личный мой путь, собственное тягло — помимо поддержки уже ранее включенных портов стандартных программ в виде апплетов, подключать то, что мне дополнительно понадобилось в моём микро-linux. Таким образом мой busybox включает всё необходимое для работы это лабы, так ebtables действительно пришлось добавить совсем недавно.
Итак. Если вас заинтересовал такой выход из положения с небольшим initrd, включающим помимо стандартных апплет ещё и такие вещи как, например, ssh/sshd/scp(dropbear), iptables, и другие, можно взять данную версию у меня. Если вы уже конфигурили initrd на основе busybox, то стоит обратить внимание, что в моей версии полноценный init-sysV, полноценные telnetd/ftpd и, потому, требуется переделать стартовые конфиги на более стандартные, не busybox-ные.
В мейнстримных дистрибутивах стартовые скрипты скорее представляют собой бесконечные многословные выкрутасы, как-будто специально предназначенные, чтобы скрыть суть происходящего. В микродистрибутиве желательно сделать всё это простым и наглядным. Вот основной стартовый скрипт нашей лабы. Да, он тоже не так уж мал, но это плата за его единость для всех гостевых хостов.
TZ=MSK-4MSD export TZ mount -a klogd syslogd -O /dev/tty6 ifconfig lo 127.0.0.1 route add -net 127.0.0.0/8 lo HOSTNAME= GATEWAY= br0_add_int() { ifconfig $1 down || exit 1 ifconfig $1 0.0.0.0 promisc up brctl addif br0 $1 || exit 1 } parse_gwhosts() { local __make_name local __f __make_name=$1 __f=`cat /etc/gwhosts | grep -v '^#' | grep tap$2 | cut -d ' ' -f $3` __f="$__make_name=$__f" eval $__f } setup_lab() { a="52:54:00:12:34:0" e=$1 a0=`ifconfig eth$e 2> /dev/null | grep HWaddr | awk '{ print $5 }'` if [ x"$a0" = x ]; then if [ $e -eq 0 ]; then echo Ethernet devices not found 1>&2 return 1 fi echo Ethernet device eth$e not found, but need for this lab 1>&2 return 1 fi n=`expr substr $a0 1 length $a` if [ x"$n" != x$a ]; then echo MAC=$a0 unsupport this lab 2>&1 return 2 fi n=`expr substr $a0 '(' length $n + 1 ')' 1` if [ tap$n != tap2 -a tap$n != tap3 ]; then echo "Configuring eth$e as tap$n" ifconfig eth$e tap$n netmask 255.255.255.0 up fi if [ $e -eq 0 ]; then HOSTNAME=`cat /etc/hosts | grep -v '^#' | grep tap$n | awk '{ print $3 }'` if [ x"$HOSTNAME" = x ]; then echo tap$n unsupport this lab 2>&1 return 3 fi echo Set hostname as $HOSTNAME hostname $HOSTNAME parse_gwhosts GATEWAY $n 2 if [ x"$GATEWAY" = x ]; then echo can not found default gateway for $HOSTNAME 2>&1 elif [ $GATEWAY = zero ]; then GATEWAY= fi if [ $HOSTNAME = www ]; then echo Configure VLAN2 ifconfig eth0 down ifconfig eth0 0.0.0.0 up vconfig add eth0 2 ifconfig eth0.2 $HOSTNAME up fi parse_gwhosts eth1 $n 3 if [ x"$eth1" != x ]; then eth0=$n setup_lab 1 || return 4 fi elif [ $e -eq 1 ]; then echo "Setup forwarding" echo 1 > /proc/sys/net/ipv4/ip_forward if [ $HOSTNAME = brouter ]; then echo Configure br0 brctl addbr br0 vconfig add eth0 2 ifconfig eth0 down ifconfig eth0 brouter-user promisc up br0_add_int eth0.2 || return 5 br0_add_int eth1 || return 6 brctl stp br0 off ifconfig br0 $HOSTNAME up /etc/brouting.sh eth0.2 /etc/tun.sh $HOSTNAME fi if [ $HOSTNAME = branch ]; then /etc/tun.sh $HOSTNAME route add -host www gw brouter-user fi fi return 0 } setup_lab 0 if [ $? -eq 0 ]; then if [ x$GATEWAY != x ]; then echo Set default gateway as $GATEWAY route add default gw $GATEWAY fi if [ $HOSTNAME = brouter -o $HOSTNAME = gw -o $HOSTNAME = branch ]; then echo Setup input IP filter /etc/fw_input.sh $HOSTNAME fi if [ $HOSTNAME = brouter -o $HOSTNAME = branch ]; then echo Setup FORWARD and NAT /etc/fw.sh $HOSTNAME fi echo Setup DNS echo search mydomain.ru provider.ru > /etc/resolv.conf if [ $HOSTNAME = www -o $HOSTNAME = gw ]; then echo nameserver 127.0.0.1 >> /etc/resolv.conf echo Setup DNS server /etc/sheerdns.sh $HOSTNAME else echo nameserver 192.168.1.1 >> /etc/resolv.conf fi fi inetd 23 telnetd inetd -u nobody 80 httpd -h /var/www ftpd -M inetd 21 ftpd -a acpid -l /dev/null /etc/dropbear.sh loadfont < /etc/consolefonts/koi8r-8x16 loadkmap < /etc/keymaps/ru1.bin
Попробуем разобрать этот стартовый скрипт /etc/rc по полочкам построчно. Так как у нас микродистрибутив, то и разумно использовать миниатюрную libc. В моём варианте busybox поддерживаются: uclibc или musl. Они не поддерживают glibc-шный метод установки таймзоны, но её очень просто установить через переменную окружения TZ, что и делают две первые строки стартового скрипта на GMT+4, не верьте глазам своим, там действительно смещение будет +4, а не -4, таков уж формат.
Далее первым делом монтируем все файловые, в том числе псевдофайловые системы, описанные как монтируемые по умолчанию в файле /etc/fstab:
/dev/ram0 / minix defaults 1 1 none /proc proc defaults 0 0 none /dev devtmpts defaults 0 0 none /dev/pts devpts noauto,gid=5,mode=620 0 0 none /sys sysfs defaults 0 0 /dev/vda /dos msdos noauto,umask=002,noexec,quiet 0 0
Как видите, корневая система у нас так и остаётся в виде radmdisk-а на файловой системе minix; без /proc теперь никуда, слишком многое на неё завязано; псевдосистема /dev теперь удобна, так как вернулась к формату старых Юниксов, ведь согласитесь такие записи /dev/sda и /dev/tty1 намного удобнее, чем devfsd-ные /dev/disck/id/..., от которых ушли где-то с ядрер 2.6; /dev/pts — отличное изобретение, жалко, что в стандарты долго шло; /sys так и не стала для людей, но sysctl на неё завязано. Вот и добрались до /dev/vda. Это наш образ всего диска, на котором в файловой системе fat лежат: ядро, сжатый ramdisk — initrd, загрузчик, ну и старая копия initrd-old на всякий случай. vda — это и есть virtio-blk, не IDE/SATA/SCSI, а микрореализация специализированного блочного устройства, предоставляемого qemu. Этот диск не предназначен для монтирования по умолчанию, не всегда надо менять на нём информацию, зато можно закрывать безбоязненно виртуальные машины, ничего не испортится, так как они пишут в ramdisk, а не на "физический", который, конечно, тоже образ.
Продолжаем разбирать /etc/rc. Чтобы увидеть проблемы, которые могут возникнуть при конфигурации и старте демонов, запуск klogd и syslogd мы делаем как можно раньше. Что они пишут можно смотреть на свободной 6-й консоли.
Как ни странно, но несетевые вещи мы почти все включили. Правда, вот всего несколько строчек, а система уже функциональна? Нет, не правда! Ведь до стартового скрипта у нас работает init, который читает свой конфиг и уж от туда он узнаёт, что надо запустить стартовый скрипт перед переходом в многопользовательский режим. Вот он, /etc/inittab, совсем настоящий sysV.
# These are the default runlevels: # 0 = halt # 1 = single user mode # 3 = multiuser mode (default Slackware runlevel) # 5 = unused (but configured the same as runlevel 3) # 6 = reboot # Default runlevel. (Do not set to 0 or 6) id:3:initdefault: # System initialization (runs when system boots). si:S:sysinit:/etc/rc # What to do at the "Three Finger Salute". ca::ctrlaltdel:/sbin/shutdown -r now # Runlevel 0 halts the system. l0:0:wait:/sbin/poweroff # Runlevel 6 reboots the system. l6:6:wait:/sbin/reboot # These are the standard console login getties in multiuser mode: c1:1235:respawn:/sbin/getty 38400 tty1 linux c2:1235:respawn:/sbin/getty 38400 tty2 linux c3:1235:respawn:/sbin/getty 38400 tty3 linux c4:1235:respawn:/sbin/getty 38400 tty4 linux # End of /etc/inittab
Да, я вас смутил, ничего до вызова стартового скрипта больше и не делается, даже установка реагирования на Ctrl-Alt-Del происходит позже. Начиная с пятой консоли у нас ничего нет, но как уже выше упоминалось, на 6-й выводит свой лог syslogd. Перезагрузка у нас очень быстрая, как только init убьёт все процессы, вызывается /sbin/reboot.
Далее /etc/rc включает сетевую подсистему, начиная с loopback интерфейса. Какой смысл это делать самому руками для меня всегда оставалось загадкой, но уж так повелось.
Последующие настройки делаются исходя из конкретного имя хоста, код усложнен по сравнению с типичными микродистрибутивами, ведь у нас мультимикродистирубив. О как.
Функция br0_add_int() добавляет интерфейс в мост, предварительно сбрасывая с интерфейса адрес. Функция parse_gwhosts() делает ровно то, что написано в названии: извлекает указанное во втором параметре вызова необходимое поле из /etc/gwhosts, первый параметр — имя переменной, куда необходимо поместить результат.
setup_lab() — основная функция для старта нашей лабы. Первым делом мы узнаем mac у ethN, (где N — параметр вызова этой функции), извлекаем последнюю цифру, проверяем mac на захаркоденный в скрипте первую часть выбранного mac в лабе шаблона без последней цифры, и формируем tapN.
Имея tapN, мы извлекаем из /etc/hosts имя нашего хоста, и уж согласно этому имени выбираем какие настройки далее делать. Для всех хостов устанавливаем hostname, находим маршрутизатор по умолчанию, вызывая parse_gwhosts, если в результате получим zero, то это означает, что ни адреса и ни шлюза по умолчанию не установлено. Ведь провайдерский gw — это и есть весь Интернет, а какой может быть шлюз по умолчанию у Интернета?
Так как хост www у нас подключён по VLAN2, то сбрасываем IP у eth0, включаем сабинтерфейс на нём с 801q инкапсуляцией vlan2 и уж полученному eth0.2 назначим IP адрес хоста www.
Проверяем, имеет ли хост второй эзернет в схеме. Если parse_gwhosts для третьего слова в своём конфиге вернёт не пустое имя, то вызываем рекурсивно функцию setup_lab с аргументом 1 (eth1).
Далее, если это первоначальный вызов setup_lab, то для eth0 мы уже всё сделали и завершаемся с хорошим (нулевым) кодом возврата. А для хостов со вторым интерфейсом продолжаем работу по конфигурированию. Раз у нас два интерфейса, то значить надо включить пересылку между ними.
Если это совсем специфический хост — brouter, то приступаем к настройке моста. Включаем мост br0, сбрасываем IP адреса на eth0, разбиваем транк на два vlan-а — нативный и vlan2, в мост надо включить согласно схеме eth0.2 от сети серверов с белыми адресами и исходящий на провайдера eth1. Следующим делом выключаем redirect вызывая ранее рассмотренный скрипт /etc/brouting.sh. И напоследок устанавливаем туннель, вызывая /etc/tun.sh.
Ещё один хост имеет дополнительные настройки — branch. На нём также надо запустить ответный туннель, а также организовать маршрутизацию для доступа к белым серверам через этот туннель. В данной лабе туннель у нас не шифрованный, но в реальной жизни лучше установить шифрованный туннель и безбоязненно управлять устройствами по нему.
После завершения setup_lab, мы устанавливаем полученные настройки для всех хостов — устанавливаем маршрут по умолчанию, включаем брандмауэр на вход на хостах brouter, gw и branch. На хостах типа www это делать не надо, это будет делать файервол на brouter по проходящим пакетам.
Конфигурация DNS. В данной лабе я исхожу из следующего. Наш домен mydomain.ru обслуживается DNS-сервером, установленным на хосте www, также там подняты реверсивные зоны для наших серых сетей. Так как по условию из Предисловия нам выделена не целая сеть/24, а часть, то реверсиная зона нашей белой сети отдана на конфигурирование провайдеру, где также установлена прямая зона provider.ru. Для соответствии реальной жизни, все не распознанные локально DNS запросы с DNS-сервера на www мы отправляем на рекурсивный DNS-сервер провайдера, хоть там и установлены две зоны. Это не даст нам возможности в лабе получить адрес google.com, но всевозможные эксперименты по хостам в том числе и реверсивно из IP в хосты данная конфигурация DNS обеспечит. На остальных хостах кроме gw и www /etc/resolv.conf конфигурится на адрес www, а у этих хостов на 127.0.0.1.
DNS сервер sheerdns весьма специфично конфигурится, так что разбор его конфига выходит за рамки данной статьи и желающие могут разобрать данный конфиг самостоятельно. Но большое предупреждение, порт sheerdns в моём busybox отличается от мейнстримного, обратная зона конфигурится в обратной записи 0.168.192, благодаря чему появилась поддержка wildcard '*'.0.168.192 и логичность и похожесть на типичные зоны, например у bind.
Теперь стартуем сетевые серверы. inetd в моём busybox можно выбрать в двух вариантах, как большой полноценный так и маленький по сервису на каждый вызов. В образе применён маленький, так что запуск сервисов делается так: inetd port исполняемый_файл_сервиса. telnetd сервер предназначен для проверки, что этот порт надёжно закрыт брандмауэром, http, ftp и ssh — открытые всем сервисы.
В качестве проверки открытого сервиса, а также для узнавания с какого адреса пришёл клиент после NAT, в CGI-скрипты помещён тривиальный ответчик /var/www/cgi-bin/test.cgi, показывающий REMOTE_ADDRESS:
#!/bin/sh # disable filename globbing set -f echo "Content-type: text/plain; charset=iso-8859-1" echo echo CGI/1.0 test script report: echo echo SERVER_SOFTWARE = $SERVER_SOFTWARE echo REMOTE_ADDR = $REMOTE_ADDR
Браузер в лабе выполнен в виде небольшого скрипта, обращающегося через netcat к URL http://хост/cgi-bin/test.cgi, помещенный в /bin/www.sh:
#!/bin/sh TEST_CGI="/cgi-bin/test.cgi" if [ "x$1" = x ]; then SERVER=127.0.0.1 elif expr substr "x$1" 1 2 = x- > /dev/null ; then echo Usage: "$0 [wwwserver]" exit 2 else SERVER="$1" fi echo "get http://$SERVER$TEST_CGI" echo "GET $TEST_CGI" | nc "$SERVER" 80
acpid — демон, в данном случае предназначенный только для реагирования на кнопку Power, переключая init в уровень выключения=0. Конфиг его как обычно размещен в /etc/apci.
/etc/dropbear.sh — скрипт, который запускает sshd сервер, проверяя есть ли его ключи и если надо их генерирует на новом образе. Если вы очистили ключи, то для продолжения экспериментов с перезагрузкой хостов желательно сохранить в образ новые сгенерированные ключи, которые помещаются в /etc/dropbear
#!/bin/sh ETC="/etc/dropbear" if [ ! -d "$ETC" ]; then mkdir "$ETC" || exit 1 fi if [ ! -f "$ETC/dropbear_rsa_host_key" ]; then dropbearkey -t rsa -f "$ETC/dropbear_rsa_host_key" || exit 1 fi if [ ! -f "$ETC/dropbear_dss_host_key" ]; then dropbearkey -t dss -f "$ETC/dropbear_dss_host_key" || exit 1 fi # exec sshd daemon dropbear
Фонты и клавиатура в лабе настраиваются для кодировки koi8.
Разобрав все скрипты, можно оценить каков минимальный набор утилит нам потребуется в initrd лабы. Список утилит, в том числе и не упоминаемых в скриптах, но возможно понадобившихся при тестах, включенных в мой busybox можно получить вызовом его без аргументов. Это не полный набор, это набор, который я посчитал желательным для проведения этой лабы.
Currently defined functions: [, [[, acpid, addgroup, adduser, adjtimex, arp, arping, ash, awk, basename, brctl, busybox, cal, cat, chgrp, chmod, chown, chroot, clear, cmp, cons.saver, cp, cut, date, dc, dd, df, dirname, dmesg, dnrd, dos2unix, dropbear, dropbearkey, du, ebtables, echo, egrep, env, expr, false, fgrep, find, free, freeramdisk, fsck.minix, ftp, ftpd, getopt, getty, grep, gunzip, gzip, halt, head, hostname, httpd, id, ifconfig, inetd, init, install, ip, ipcalc, iptables, iptables-restore, iptables-save, iptunnel, kill, killall, klogd, less, ln, loadfont, loadkmap, logger, login, losetup, ls, lspci, mc, mesg, mkdir, mkfs.minix, mknod, more, mount, mv, nc, netstat, nice, nohup, nslookup, ocxb, openvt, passwd, ping, poweroff, printenv, printf, ps, pwd, reboot, reset, rm, rmdir, route, scp, sed, seq, setconsole, setkeycodes, sh, sheerdns, shutdown, sleep, sort, ssh, stat, stty, su, sync, sysctl, syslogd, tail, tar, tee, telnet, telnetd, test, top, touch, tr, traceroute, true, tty, umount, uname, uniq, unix2dos, uptime, usleep, vconfig, watch, watchdog, wc, who, xargs, yes, zcat
Как видите, тут даже есть mc (Midnight Commander)! Отличия от мейнстримного busybox: полноценный init, less, shutdown, мой редактор ocxb (почему он так назван в 96 году я тоже уже не помню), cons.saver — это утилитка к mc, уже упоминаемые ebtables, iptables, dropbear/ssh/scp, dnrd, sheerdns...
Вот мы и добрались к последней главе, где собственно и будут описан набор тестов, ради которых всё вышеизложенное и писалось. Приложенный образ лабы уже содержит все настройки для успешного прохождения тестов, так что во время демонстрации работы мы будем потихоньку всё ломать, благо запись в образ необходимо производить специальной командой. В образе заведены два пользователя без паролей: root и пользователь, который у меня уже 30 лет в юниксах — dzo (я уже забыл, что оно означает).
Мой стартовый скрипт для пуска этой лаборатории. Тут также демонстрируется bash-изм, не работающий в busybox-овом d(ash): local var=value и eval сложная_строка, что упрощает трюк с присвоением результата функции в имя переменной, передаваемого как параметр. Локальные переменные должны быть уникальны, и, то есть "глобальны" к основному телу скрипта, иначе этот трюк не сработает, потому они и имеют такие странные имена с подчёркиваниями. Ещё скрипт содержит небольшой трюк, позволяющий сделать только интерпретаторы: имя переменной не существует при вызове и формируется разбирая конфигурационную строку, а присваивание осуществляется через мощное средство — eval. В конфигурационной строке указаны хосты и их tap интерфейсы.
#!/bin/bash # # Usage # # lab_start.sh [all] [[-]host ...] # start hosts (all) -do not start this hosts HOSTS="user:1 www:2 brouter:3:4 gw:5:6 branch:7:8 branch_host:9" . ./lab0.sh cut_host() { local __make_name=$1 local __c=`echo $2 | cut -d':' -f$3` eval $__make_name="'$__c'" } make_tap_arg() { local __make_name=$1 local __c="-net tap,vlan=$2,script=no,downscript=no,ifname=tap$2" eval $__make_name="'$__c'" } make_nic_arg() { local __make_name=$1 local __c="-net nic,vlan=$2,model=virtio,macaddr=52-54-00-12-34-0$2" eval $__make_name="'$__c'" } make_net_args() { unset nic$2 tap$2 cut_host t $1 $(($2+1)) if [ x$t != x ]; then make_nic_arg nic$2 $t make_tap_arg tap$2 $t echo -n " tap$t" return 0 fi return 1 } start() { echo -n start $1 make_net_args $2 1 if [ $? -ne 0 ]; then echo "Do not found tap's number for host $1" 2>&1 exit 1 fi make_net_args $2 2 echo qemu-system-x86_64 -name $1 \ -boot c -m 64M -enable-kvm \ -drive file=$IMG,if=virtio,format=raw \ $nic1 $tap1 $nic2 $tap2 \ -display sdl -vga vmware \ -parallel none -serial none -balloon none -localtime -daemonize } s=x$1 if [ $s = x ]; then s=xall fi while [ $s != x ]; do found=0 for ht in $HOSTS; do cut_host h $ht 1 if [ $s = xall -o $s = x$h ]; then eval $h=1 found=1 elif [ $s = x-$h ]; then eval $h=0 found=1 fi done if [ $found -eq 0 ]; then minus=`expr substr $s 1 2` if [ $minus != x- ]; then minus= else minus="-" fi echo "$0: "$minus"host '$1' unknown" 2>&asp;1 exit 1 fi shift s=x$1 done for ht in $HOSTS; do cut_host h $ht 1 eval s=\$$h if [ x$s = x1 ]; then start $h "$ht" fi done
Этот скрипт позволяет либо полностью запустить лабораторию при вызове без ключей, либо вызвать указанные хосты, либо всё либо указанное без хостов, предварив их знаком минус. И тут не обойдёмся без трюка: запустить незапущенные хосты при уже некоторых запущенных, очень просто: вызвав скрипт без параметров qemu обругается, что tap-ы у запущенных хостов уже заняты и, стартанут только те хосты, у которых tap-ы свободны. Желающие могут вынести вверх другие параметры вызова qemu — размер памяти каждому гостю (тут — 64МБ) и др.
Погодите-ка, а у меня запускается только www, а где остальные виртуальные хосты? Чтож, если у вас запустился www, то поздравляю, у вас как минимум уже сконфигурен tap1, ведь скрипт по поднятию всех необходимых tap интерфейсов и мостов ещё не приведён. Вот он:
#!/bin/sh BR=br nr_br=1 . ./lab0.sh add_int() { ifconfig $1 down ifconfig $1 0.0.0.0 promisc up brctl addif $2 $1 } br_close() { ifconfig $1 down > /dev/null 2> /dev/null brctl delbr $1 > /dev/null 2> /dev/null } br_open() { local b=$BR$nr_br br_close $b brctl addbr $b for I in `seq $1 $2`; do if ! ifconfig tap$I > /dev/null 2> /dev/null; then tunctl -t tap$I -u $USER fi add_int tap$I $b done brctl stp $b off ifconfig $b up nr_br=$((nr_br+1)) } if [ x"$1" = xstart ]; then br_open 1 3 br_open 4 5 br_open 6 7 br_open 8 9 elif [ x"$1" = xclose ]; then br_close "$BR"1 br_close "$BR"2 br_close "$BR"3 br_close "$BR"4 else echo usage: $0 'start|close' 1>&2 exit 1 fi
Его надо запускать от суперпользователя один раз после перезагрузки хостовой машины. С параметром start производится включение с доступом пользователю USER нужного количества tap интерфейсов, помещение их в мосты соответствующих наборов функцией br_open, где вызов, например, br_open 1 3, конфигурит tap1, tap2 и tap3 и помещает их в мост br1. Следующий вызов br_open помещает tap-ы в br2.
Какой набор тестов можно провести? Лаборатория позволяет проверить работоспособность brouter-а, его прозрачного брандмауэра, туннеля, DNS и провести другие эксперименты. Как уже упоминалось, брандмауэр должен обеспечивать следующие условия: разрешать доступ к сервисам ssh, ftp, www, dns для всех. В локальной сети и через туннель должен остаться доступным сервис telnet, но на хосте GW и из него telnet должен быть не доступным.
Как проверить работу brouter-а? В busybox-е (в моём и мейнстримном) помещён мой порт BSD-вариант traceroute. В отличии от современных полноценных Linux-дистрибутивов с GNU-traceroute, наш вариант имеет ключик -l, показывающий на сколько у вернувшихся пакетов уменьшился ttl (на самом деле это он сам вычисляет, так как traceroute посылает ttl от 1 до max_hop..., ну и так далее, это не тема данной статьи). Зачем это всё нужно? Зная исходный ttl и его изменения мы можем увидеть, проходил ли пакет маршрутизацию или был переслан по brouting-у. Если была пересылка без маршрутизации, то, несмотря на добавившиеся hop-ы, ttl у этих пакетов не уменьшается! Тем самым вы можем увидеть, выключен ли brouting в этом месте сети.
Как проверить работу DNS, если все хосты мы и так поместили в /etc/hosts каждой виртуальной машины из единого образа? А наш DNS умеет wildcard '*'.mydomain.ru и '*'.provider.ru. При обращении к хосту, в доменах mydomain.ru или provider.ru, не прописанному в hosts, должен сработать DNS и выдать CNAME на www.mydomain.ru или gw.provider.ru соответственно.
Отключить брандмауэр можно вызовом /etc/no_fw.sh
#!/bin/sh iptables -F INPUT iptables -P INPUT ACCEPT ./fw.sh stop
После этого действия должно произойти следующее: telnet станет доступным для всех хостов, кроме тех, которые должны идти через NAT, ведь NAT тоже выключится. Логично предположить, что провайдер, узнав о наших серых сетях и их маршрутизаторе весьма с малой вероятностью, но таки может умудриться выставить маршрут к ним. По умолчанию в лаборатории у GW нет никаких маршрутов, кроме выставленных ядром для сконфигурённых поднятых интерфейсов. Если провайдер, например, добавит маршрут к сети USER-NET, то при выключенном брандмауэре на brouter-е доступ к telnet сервису USER появится и у GW, ведь USER не имет собственного брандмауэра, а без NAT-а стаботает открытая на распашку маршрутизация куда угодно.
Ну что ж, теперь результаты. От хоста www.
www $ traceroute -l xxx.provider.ru traceroute to gw.provider.ru (192.168.1.6), 30 hops max, 46 byte packets 1 brouter.mydomain.ru (192.168.1.5) 0.344 ms (64) 0.219 ms (64) 0.203 ms (64) 2 gw.provider.ru (192.168.1.6) 0.473 ms (64) 0.395 ms (64) 0.255 ms (64) www $ traceroute -l branch-gw traceroute to branch-gw.mydomain.ru (192.168.3.1), 30 hops max, 46 byte packets 1 brouter.mydomain.ru (192.168.1.5) 0.168 ms (64) 0.191 ms (64) 0.136 ms (64) 2 branch-gw.mydomain.ru (192.168.3.1) 0.689 ms (63) 0.465 ms (63) 0.390 ms (63) www$ telnet user Trying 192.168.0.2... Connected to user. Escape character is '^]'.
Первый трейс демонстрирует работу DNS и brouter-а, транслируя неизвестное имя xxx.provider.ru по wildcard в gw.provider.ru, а если мы присмотримся в значения ttl указанных в скобках, то можно заметить, что ttl не меняется. То есть на лицо работа прозрачного брандмауэра, не смотря на то, что он указан как маршрутизатор по умолчанию в Интернет.
Второй трейс демонстрирует работу туннеля без использования дополнительных сетей. Тут специально выбран адрес второй стороны из внутреннего, не доступного из Интернет интерфейса. ttl тут уже ведёт себя как при обычной маршрутизации, уменьшаясь с каждым хопом, что и логично.
Доступ к telnet к серой сети осуществляется. К gw при включенном брандмауэре — нет.
Проверка работы NAT осуществляем из серой сети в Интернет и на хост филиала:
user $ www.sh gw get http://gw/cgi-bin/test.cgi HTTP/1.0 200 OK Content-type: text/plain; charset=iso-8859-1 CGI/1.0 test script report: SERVER_SOFTWARE = busybox httpd/2.2 22-Apr-2006 REMOTE_ADDR = 192.168.1.5 user $ www.sh branch-host get http://branch-host/cgi-bin/test.cgi HTTP/1.0 200 OK Content-type: text/plain; charset=iso-8859-1 CGI/1.0 test script report: SERVER_SOFTWARE = busybox httpd/2.2 22-Apr-2006 REMOTE_ADDR = 192.168.0.2
Как видим, наш адрес замаскарадился к внешнему адресу NAT-а — brouter-а при обращении к Интернет-ресурсу и остался без изменения при обращении к нашему хосту в филиале. Между прочим, последний тест не ради воды в статье, небрежное конфигурирование NAT может привести к маскарадингу адреса и в те сети, для которых трансляция адресов не требуется, потому и вредна, например, так как скроет адрес пользователя, сломавшего всё в филиале.
Начинаем ломать. Включим brouting. Теперь трейс с www до gw изменится. Не станет прыжка через brouter, будет только один hop. К сожалению, увидеть icmp redirect пакет утилиты из busybox не могут, данный тест можно провести заменив гостевой хост WWW самой хостовой машиной с полноценной утилитой ping. Вот тут то и проявилась обратная сторона уменьшения объёма утилит за счёт функциональности.
Добавим маршрут на GW до нашей серой сети. Пинг до серой сети появился. Устройство нашего брандмауэра таково, что icmp-req/icmp-echo он для всех разрешает, но сервисы открывает только для сервера www из сети центрального офиса. При рабочем брандмауэре, с хакерски добавленной серой сетью, из gw всё равно не возможно получить доступ не только к telnet, но и к ftp и http на хостах серой сети.
gw # ping user PING user.mydomain.ru (192.168.0.2): 65 data bytes ping: sendto: Netword is unreachable gw # route add -net user-net/24 gw brouter gw # ping user PING user.mydomain.ru (192.168.0.2): 65 data bytes 84 bytes from 192.168.0.2: icmp_seq=0 ttl=63 time=0.9 ms ^C gw # www.sh user get http://user/cgi-bin/test.cgi (долго ждём) nc: connect: Connection timed out