Прозрачный брандмауэр с маршрутизатором. Тестовая лаборатория

Автор: Владимир Олейник <dzo@simtreas.ru> (C) 2017

Содержание

Предисловие

Жили 20 лет не тужили, имея подключение к провайдеру по pppoe, сетку/24. Филиалы подключали ко внутренней сети через туннели, благо трафик по области был безлимитный, а в Интернет безлимитным был только трафик центрального офиса. Маршрутизатор был нами бережно настроен как центральный брандмауэр на входе, все маршруты замыкались на нём, то есть на остальных хостах можно было ограничиться маршрутом по умолчанию и не беспокоиться о проникновении извне.

По независящим от нас и не касающимся данной статьи причинам, с нового года поменялся провайдер. Выдал он нам небольшую сетку, сказал адрес маршрутизатора по умолчанию, находящийся в этой же сети. В общем, ситуация вполне стандартная. Удивительно, что при такой ситуации в сети не разбираются проблемы такого подключения.

А проблемы сразу заметны, ведь маршрутизатор не под нашим управлением:

  1. Все хосты с "белыми" адресами должны обзавестись собственными брандмауэрами.
  2. Маршрутизация превращается либо в динамическую (что то ещё удовольствие), либо в прописывании её на всех "белых" хостах.

Быстрые и неправильные решения

Данные проблемы можно попытаться решить наскоком, и что может ложно успокоить, "всё будет работать". Поменяв маршрут по умолчанию на белых хостах на собственный маршрутизатор создастся видимость, что всё починилось: маршрутизация до филиалов и серых сетей появилась, а брандмауэр даже сможет срабатывать на выход. Но кому более всего нужен брандмауэр на выход? Вот то-то же. Да и с маршрутизацией не всё на самом деле в порядке.

На каждый посланный вами из белой сети в Интернет пакет, вы будете получать 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). Не такая уж и маленькая лаборатория.

Трюки, трюки

  1. Почему вся нумерация на предыдущей картинке начинается с единицы, ведь можно и с нуля? Можно. Я исходил из предпосылки, что вы уже работается с виртуалками, скорее всего интерфейс tap0 и br0 у вас возможно занят как и у меня.
  2. Почему на картинке так схематично нарисован туннель? Разве не нужно создавать адресацию и интерфейсы? Это хороший вопрос! Один из интересных трюков данной статьи, с которым я сейчас спешу с вами поделиться.
  3. В отличии от других ОС, Linux с point-to-point интерфейсами очень универсален и своеобразен, взяв самое лучшее от других ОС. Многие даже не догадываются, что настоящих ptp интерфейсов в Linux нет! Кто-то наверное помнит, что у ppp-интерфейса когда-то было можно было указать произвольную маску. Так вот, это можно сделать и сейчас у любого ptp-интерфейса. Зачем это надо? А вот для туннелей! Эта маска на самом деле половинчатая, на локальном адресе туннеля интерфейс ведет себя честным образом как ptp. Этот адрес в маршрутизации не участвует, его можно назначить, а это очень удобно, (в терминологии cisco — unnumbered) либо из существующей сети, озаботившись proxy-arp, либо даже из любого имеющегося адреса на хосте. А маска работает для удаленной стороны. Тем самым для туннелирования одной сети нам не понадобятся ни дополнительные "левые" сети, ни скрипты для манипулирования маршрутизацией, которые по своей сути не могут не иметь названия "из соплей и палок" в связи с некоторыми лагами по реагированию на события: интерфейс поднялся и тут же упал. Локальный внешний адрес туннеля будет адресом внешнего эзернет-интерфейса, удалённый внешний адрес — внешним адресом второго конца туннеля (это cisco делать может), а внутренние адреса будут соответсвенно: локальный — адресом внутреннего эзернет-интерфейса, удалённый — внутренним адресом второго конца туннеля, маска — маске туннелируемой удаленной сети. Вот это cisco делать не умеет!

  4. Ещё один основной трюк данной статьи в том, что мы сделаем один образ виртуальной машины. Как же будет она распознавать какая она из схемы? А у нас есть параметр, о котором придётся позаботиться, настроив его разным для каждого виртуального хоста — MAC адрес интерфейса. Если мы будем назначать его по алгоритму, то этим же алгоритмом можно узнать какой хост запущен внутри виртуальной машины. В нашей лаборатории "всего" 9 интерфейсов, так что давайте для простоты будем использовать последнюю цифру MAC-адреса, даже манипуляции с 16-ричной системой производить не потребуется.
  5. Занятный малюсенький трюк: почему так любят адреса 192.168.X.Y? А потому что это адреса класса C, нам не придётся указывать маску в командах ifconfig. Так и что? А тогда пришлось бы делать еще один конфигурационный файл, где будут указаны не только адреса (а такой конфиг из-за трюка с mac и hosts и не понадобится), но и маски. И тут уже не выкрутишься, либо харкодить, например, маску/24 для других серых адресов (10/8, 172.16/12), которые не класса C.
  6. Внимательный читатель найдёт ещё небольшой трюк в скриптах, он очень удобен, хотя для (d)ash немного многословен и многоскобочен-многокавычен: некоторые функции записывают результат в переданный параметр как имя переменной. Это предотвращает вызов fork() для стандартного способа для таких случаев: var=`foo args`, я делаю это так: foo var args. Согласитесь, это и нагляднее и выход из положения, так как shell-скрипты могут возвращать только цифровой небольшой код возврата. Первый раз такое встретится в функции swap() в скрипте /etc/tun.sh. Как уже говорилось, на первый взгляд обилие значков поначалу пугает, в bash это делается намного проще. Но согласитесь, вызов swap LOCAL REMOTE намного нагляднее, чем TMP=$LOCAL LOCAL=$REMOTE REMOTE=$TMP. Bash-изм, упрощающий работу с данным трюком можно увидеть в скрипте запуска лаборатории (см. последнюю главу).

Приступаем к конфигурированию общих конфигов

Для удобства работы не с 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, возможно уже знает как создать мини-дистрибутив для тестов. Но я с самого начала планировал посвятить большой объём этой статьи именно этому моменту — функционирования виртуальных хостов на моём варианте микро-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МБ. Этот параметр надо согласовать с конфигом ядра, в ядре должно быть установлено не менее.

А что же всё таки в initrd?

Ну вот, наконец, мы и подошли к тому моменту, к которому я шёл более 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