- PVSM.RU - https://www.pvsm.ru -
Одним из вариантов повышения стабильности подключения к сети Интернет является использование двух внешних каналов связи, что подразумевает автоматическое переключение между ними. В статье кратко рассмотрены некоторые варианты решения данной задачи. Предложен свой способ решения с использованием скриптов на языке bash в ОС FreeBSD, приведены инструкции по созданию конечной системы и исходные тексты необходимых для этого скриптов.
Для повышения стабильности подключения к сети Интернет корпоративные решения подразумевают использование двух и более внешних сетевых каналов. Их одновременное (например, методом балансировки) или поочередное (с переключением между каналами) использование является не совсем тривиальной, однако уже решенной множеством способов задачей. Вот некоторые из них:
Каждый из вышеперечисленных подходов имеет свои достоинства и недостатки. Вариант первый, SOHO-маршрутизаторы:
Достоинства:
Недостатки:
Второй вариант, коммутаторы Layer 3:
Достоинства:
Недостатки:
Третий вариант, скрипты переключения:
Достоинства:
Недостатки:
Четвертый вариант, балансировка правилами NAT:
Достоинства:
Недостатки:
Имеются сомнения относительно скорости работы в случае «падения» одного из внешних каналов.
И, наконец, пятый вариант, использование proxy-сервера:
Достоинства:
Недостатки:
В начале разработки, несколько лет назад, был выбран вариант написания собственного скрипта по следующим причинам. Во-первых, цена. По этому критерию отпадают коммутаторы Layer 3 из второго пункта. В условиях локальной сети на 10 машин решения корпоративного уровня – непозволительная роскошь. Об устройствах из первого пункта автор в момент принятия решения, увы, не знал. Кстати говоря, сейчас они не подходят уже по пункту «стабильность». А решение из четвертого пункта не подходит, т.к. имеющиеся интернет-каналы различаются в десятки раз по скорости и использование подобной схемы, на мой взгляд, не обосновано. Кроме того, добавляются сомнения относительно качества связи с внешней сетью в случае «падения» одного из каналов. Пятый же пункт не устраивает, во-первых, замедлением скорости потока, во-вторых – хотелось бы иметь независимое от необязательных компонентов решение. Соответственно, оставался пункт 3, где после исследования чужих скриптов и попыток их адаптировать, было решено отказаться от этой идеи и написать свой скрипт.
Со временем рядом с основным «маршрутизатором» на FreeBSD был установлен резервный, настройки dns, dhcp, nat и ipfw не раз претерпевали изменения. Всё постепенно развивалось и улучшалось, кроме вышеупомянутого скрипта, который в итоге было решено переписать, используя, как основополагающие, следующие принципы: модульность, единый файл настроек, а также гибкость и простоту настройки в любой unix-подобной системе, а также простоту добавления новых модулей.
Какова же конечная цель данного проекта? Создать универсальный и легко масштабируемый программный комплекс на основе системы клиент-сервер (хотя правильнее было бы назвать ее агент-сервер), ориентированный на определение неполадок во внешних и внутренних соединениях и автоматическое переключение на работоспособные соединения. В качестве агента в данном случае выступает «сборщик» информации о состоянии внешних и внутренних соединений на текущий момент времени, а в качестве сервера – часть программы, принимающая решение о том, какое соединение приоритетно и при необходимости отдающая команды на переключение на это соединение. При этом на сервере (в данном контексте) агент может не функционировать.
Итак:
Подробности алгоритма можно расписывать очень долго, выше лишь изложена общая суть. Не спорю, что и n, и m (из примера выше) принимают значения больше 2-х крайне редко, но встречаются, поэтому, почему бы не сделать универсальное средство?
В процессе написания скриптов я столкнулся с некоторыми ограничениями языка bash, так что в настоящий момент весьма смутно представляется более элегантное решение вышеописанной задачи. Пока что имеется решение для отдельно стоящего «маршрутизатора», разработанное с ориентиром на дальнейшее расширение возможностей.
В силу многих причин было решено использовать в качестве основы локальной сети, а также шлюза в интернет старую машину(Pentium 3, 512 ОП) с FreeBSD, на текущий момент версии 9.2. Впоследствии для повышения надежности была установлена вторая подобная машина, которая работает в паре с уже имеющейся. Кстати говоря, за прошедшие два года поломок было ровно две – в первый раз вышел из строя БП, во второй – одна из сетевых карт. Стоит учесть, что при этом вся локальная сеть работала без нареканий, так как в случае сбоя вступала в игру дублирующая машина. Так что использование старого железа в данной схеме практически не сказывается на стабильности работы сети. Также имеется 2 внешних канала от разных интернет-провайдеров. Общая схема приведена ниже, на ней:
Синие и красные стрелки – внешние каналы связи.
Черные стрелки – внутренние каналы связи.
Выглядит данная система так:
Коммутатор разделяет трафик от провайдеров с помощью vlan-ов. В конкретном случае это Cisco SF300-08.
Поподробнее, что и с помощью чего работает на самих машинах:
Firewall — IPFW
NAT – «ядерный» NAT из IPFW.
DNS – Bind 9 (используется последняя версия для FreeBSD)
DHCP – isc-dhcpd
ToFoIn – главный виновник данной статьи.
В статье не будут описаны тонкости настройки DNS, DHCP, так как, вообще говоря, предполагается, что читатель уже знаком с подобными системами. Вдобавок, материалов на эту тему полно, и некоторые ссылки будут упомянуты в конце статьи. В технической части приведены полные правила Firewall и NAT для ipfw практически без комментариев (опять-таки, материалов на эту тему также полно), которые имеются на настоящий момент, а также параметры ядра и rc.conf.
Теперь подробно рассмотрим принцип действия скрипта. Для начала, — какие имеются модули и их функции:
Daemon – как и следует из названия, — основной процесс, который по таймеру запускает модули тестирования и переключения.
Tester – тестирует наличие связи по внешним каналам с помощью команды ping.
Judge – исходя из результатов тестов определяет, какой внешний канал работает и необходимо ли переключение.
Logger – отвечает за ведение журнала событий. Необходим для того, чтобы информация о событиях не дублировалась и журнал проще читался.
Watchdog – запускается по расписанию из crontab. Определяет «зависания» всех модулей и, по возможности, пытается решить возникшие проблемы.
Помимо самих скриптов, стоит рассмотреть еще некоторые важные файлы:
Tofoin.conf – единый файл настроек.
Tofoin.log – единый файл журнала событий.
Result_<внутренний номер канала> — рабочий файл, сюда «складываются» результаты тестирования
Также используется еще некоторое количество рабочих файлов и, разумеется, каждый скрипт при запуске создает pid-файл, а в процессе завершения работы его удаляет.
Работа Logger-а и Watchdog-а не будет детально описана, кому интересно, сможет при желании ознакомиться. Рассмотрим подробнее работу основных модулей, т.е. Daemon, Tester и Judge. Daemon запускает Tester и Judge по таймерам, которые хранятся в конфигурационном файле. Выглядит это следующим образом – при старте запускаются тесты, а также запоминается timestamp, далее, исходя из чувствительности, каждые n секунд проверяется, превышено ли время для запуска следующего тестирования, либо оценки текущего состояния наличия связи. Таким образом, Daemon помнит последние timestamp для тестов и проверки и сравнивает их с текущим timestamp. Если разница больше, чем указано в конфигурационном файле, то запускается, соответственно, тест или проверка и timestamp заменяется на текущий. И т.д.
Tester – самый простой, пока что, модуль. Принимает на входе 2 переменные следующим образом:
./tester.sh a b
, где a – номер таблицы маршрутизации, b – задача (в обычном варианте b=10, что означает полное тестирование и запись результата).
Также для модуля Tester предусмотрены пробные режимы, где b=0 – ping только первой цели (из конфигурационного файла), b=1 – ping только второй цели (из конфигурационного файла), b=<назначение>, к примеру, b=habrhabr.ru – в этом режиме производится ping произвольной цели. В данном случае для 0 таблицы маршрутизации команда будет выглядеть следующим образом:
./tester.sh 0 habrahabr.ru
Основным компонентом программы, очевидно, является модуль Judge. Алгоритм его работы в общих чертах таков:
Разумеется, все ключевые действия записываются в журнал событий, а в случае возникновения нештатной ситуации, опять же, записывается причина ошибки и вызывается Watchdog.
Итак, основные принципы работы рассмотрены, предлагаю ознакомиться с тем, как это всё реализовано на практике.
Про оборудование уже упоминалось, в данном же разделе попробую рассказать поподробнее. Для обеспечения работы DNS, DHCP, NAT и IPFW в моем случае (внутренняя сеть примерно на 30 машин) вполне хватает Celeron на базе Pentium III, 512 Мб оперативной памяти и HDD на 40Гб, а также БП на 350W с поддержкой соответствующих разъемов материнской платы. Также подсоединено по 2 дополнительные PCI сетевые карты. По мощности оба маршрутизатора примерно одинаковы.
Здесь можно возразить, что мощности местами даже излишни, однако данные машины специально не закупались, а были собраны из того, что осталось после обновления парка пользовательских машин. Скорее всего, минимально необходимый набор сервисов можно запустить и на куда более слабом железе. Также было бы неплохо подстраховаться и организовать зеркальный RAID. К сожалению, я заранее об этом не подумал и теперь это связано с некоторыми сложностями, но это уже совсем другая история.
На мой взгляд, это вполне достойное применение старого рабочего железа, которое в противном случае зачастую либо пылится на складе, либо выкидывается или раздается.
Для того, чтобы данная система заработала, разумеется, необходимо выполнить некоторую предварительную настройку.
Во-первых, настроить Primary и Secondary DNS-сервера. Если у вас только один «маршрутизатор», то для начала достаточно только Primary DNS-сервера. В данной задаче использовался, как и было упомянуто, Bind 9. Некоторые ссылки по настройке даны в конце статьи. Очень хорошо в этом случае помогает учебник «DNS и BIND» Крикета Ли и Пола Альбитца.
Во-вторых, нужно настроить dhcp failover peer. Если у вас только один «маршрутизатор», то хватит обычных настроек для standalone DHCP сервера. Опять же ссылки приведены в конце статьи. На случай, если по каким-либо причинам статья о настройке failover dhcp peer по ссылке будет недоступна (а в последние несколько месяцев ситуация именно такая), приведу здесь скрипт для синхронизации настроек, а также ключевые моменты по настройке.
# dhcpd.conf
#
# option definitions common to all supported networks...
option domain-name "companyname.local";
option domain-name-servers 10.0.0.2, 10.0.0.1;
option ntp-servers 10.0.0.2, 10.0.0.1;
option log-servers 10.0.0.1;
update-static-leases on;
# 1 hour
default-lease-time 3600;
# 1 day
max-lease-time 86400;
# Use this to enable / disable dynamic dns updates globally.
ddns-update-style interim;
# If this DHCP server is the official DHCP server for the local
# network, the authoritative directive should be uncommented.
authoritative;
# Use this to send dhcp log messages to a different log file (you also
# have to hack syslog.conf to complete the redirection).
log-facility local7;
set vendorclass = option vendor-class-identifier;
# DNS key
include "/usr/local/etc/dhcpd/dns.key";
zone companyname.local.{
primary 127.0.0.1;
key DHCP_UPDATER;
}
zone 0.0.10.in-addr.arpa.{
primary 127.0.0.1;
key DHCP_UPDATER;
}
# DHCP Failover, Primary
include "/usr/local/etc/dhcpd/dhcpd.conf_primary";
# Subnet declaration
include "/usr/local/etc/dhcpd/dhcpd.subnet";
# Static IP addresses
include "/usr/local/etc/dhcpd/dhcpd.static";
Здесь dns.key – ключ для связи с dns сервером, данные вопросы подробно рассмотрены в статьях по настройке dns+dhcp.
##########################
# DHCP Failover, Primary #
##########################
failover peer "dhcpdpeer" { # Failover configuration
primary; # I am the primary
address 10.0.0.1; # My IP address
port 1111;
peer address 10.0.0.2; # Peer's IP address
peer port 2222;
max-response-delay 60;
max-unacked-updates 10;
mclt 3600;
split 128; # Leave this at 128, only defined on Primary
load balance max seconds 3;
}
subnet 10.0.0.0 netmask 255.255.255.0 {
pool {
failover peer "dhcpdpeer";
range 10.0.0.15 10.0.0.240;
}
option subnet-mask 255.255.255.0;
option routers 10.0.0.2, 10.0.0.1;
option broadcast-address 10.0.0.255;
option netbios-name-servers 10.0.0.3;
option netbios-dd-server 10.0.0.3;
option netbios-node-type 8;
}
В данном случае netbios name server – windows сервер с запущенной службой wins сервера, также в этой роли может выступать samba.
host SERVER3 {
hardware ethernet 11:11:11:11:11:11;
fixed-address 10.0.0.3;
}
host SERVER4 {
hardware ethernet 22:22:22:22:22:22;
fixed-address 10.0.0.4;
}
Данный файл, как нетрудно догадаться, для статических адресов.
# dhcpd.conf
#
# option definitions common to all supported networks...
option domain-name "companyname.local ";
option domain-name-servers 10.0.0.2, 10.0.0.1;
option ntp-servers 10.0.0.2, 10.0.0.1;
option log-servers 10.0.0.1;
update-static-leases on;
# 1 hour
default-lease-time 3600;
# 1 day
max-lease-time 86400;
# Use this to enable / disable dynamic dns updates globally.
ddns-update-style interim;
# If this DHCP server is the official DHCP server for the local
# network, the authoritative directive should be uncommented.
authoritative;
# Use this to send dhcp log messages to a different log file (you also
# have to hack syslog.conf to complete the redirection).
log-facility local7;
set vendorclass = option vendor-class-identifier;
# DNS key
include "/usr/local/etc/dhcpd/dns.key";
zone companyname.local.{
secondary 127.0.0.1;
key DHCP_UPDATER;
}
zone 0.0.10.in-addr.arpa.{
secondary 127.0.0.1;
key DHCP_UPDATER;
}
# DHCP Failover, Primary
include "/usr/local/etc/dhcpd/dhcpd.conf_secondary";
# Subnet declaration
include "/usr/local/etc/dhcpd/dhcpd.subnet.DONOTEDIT";
# Static IP addresses
include "/usr/local/etc/dhcpd/dhcpd.static.DONOTEDIT";
###########################
# DHCP Failover,Secondary #
###########################
failover peer "dhcpdpeer" { # Failover configuration
secondary; # I am the secondary
address 10.0.0.2; # My IP address
port 2222;
peer address 10.0.0.1; # Peer's IP address
peer port 1111;
max-response-delay 60;
max-unacked-updates 10;
mclt 3600;
load balance max seconds 3;
}
Остальные файлы можно взять от первого «маршрутизатора», только изменив название, либо настроить до конца и файлы переместятся автоматически при перезапуске isc-dhcpd (о том, как именно – ниже).
#!/bin/sh
# backup generation
date=`date -v-1d '+%Y%m%d-%H%M%s'`
month=`date '+%m%Y'`
sudo -u dhcp-updater cp -f /usr/local/etc/dhcpd/dhcpd.subnet /var/dhcp-backup/dhcpd.subnet.$date
sudo -u dhcp-updater bzip2 -f -k -z /var/dhcp-backup/dhcpd.subnet.$date
sudo -u dhcp-updater tar -r -f /var/dhcp-backup/dhcpd.subnet.$month.tar -C /var/dhcp-backup dhcpd.subnet.$date.bz2
sudo -u dhcp-updater cp -f /usr/local/etc/dhcpd/dhcpd.static /var/dhcp-backup/dhcpd.static.$date
sudo -u dhcp-updater bzip2 -f -k -z /var/dhcp-backup/dhcpd.static.$date
sudo -u dhcp-updater tar -r -f /var/dhcp-backup/dhcpd.static.$month.tar -C /var/dhcp-backup dhcpd.static.$date.bz2
sudo -u dhcp-updater scp -P 22 -q /var/dhcp-backup/dhcpd.subnet.$date.bz2 dhcp-updater@10.0.0.2:/var/dhcp-backup
sudo -u dhcp-updater ssh -p 22 10.0.0.2 tar -r -f /var/dhcp-backup/dhcpd.subnet.$month.tar -C /var/dhcp-backup dhcpd.subnet.$date.bz2
sudo -u dhcp-updater scp -P 22 -q /var/dhcp-backup/dhcpd.static.$date.bz2 dhcp-updater@10.0.0.2:/var/dhcp-backup
sudo -u dhcp-updater ssh -p 22 10.0.0.2 tar -r -f /var/dhcp-backup/dhcpd.static.$month.tar -C /var/dhcp-backup dhcpd.static.$date.bz2
sudo -u dhcp-updater ssh -p 22 10.0.0.2 rm /var/dhcp-backup/dhcpd.subnet.$date.bz2
sudo -u dhcp-updater ssh -p 22 10.0.0.2 rm /var/dhcp-backup/dhcpd.static.$date.bz2
sudo -u dhcp-updater rm /var/dhcp-backup/dhcpd.subnet.$date
sudo -u dhcp-updater rm /var/dhcp-backup/dhcpd.static.$date
sudo -u dhcp-updater rm /var/dhcp-backup/dhcpd.subnet.$date.bz2
sudo -u dhcp-updater rm /var/dhcp-backup/dhcpd.static.$date.bz2
# sync and restart secondary DHCP
sudo -u dhcp-updater scp -P 22 -q /usr/local/etc/dhcpd/dhcpd.subnet dhcp-updater@10.0.0.2:/usr/local/etc/dhcpd/dhcpd.subnet.DONOTEDIT
sudo -u dhcp-updater scp -P 22 -q /usr/local/etc/dhcpd/dhcpd.static dhcp-updater@10.0.0.2:/usr/local/etc/dhcpd/dhcpd.static.DONOTEDIT
sudo -u dhcp-updater ssh -p 22 10.0.0.2 sudo /usr/local/etc/rc.d/isc-dhcpd restart
dhcpd_checkconfig ()
{
local rc_flags_mod
setup_flags
rc_flags_mod="$rc_flags"
# Eliminate '-q' flag if it is present
case "$rc_flags" in
*-q*) rc_flags_mod=`echo "${rc_flags}" | sed -Ee 's/(^-q | -q | -q$)//'` ;;
esac
if ! ${command} -t -q ${rc_flags_mod}; then
err 1 "`${command} -t ${rc_flags_mod}` Configuration file sanity check failed"
fi
}
dhcpd_checkconfig ()
{
local rc_flags_mod
setup_flags
rc_flags_mod="$rc_flags"
# Eliminate '-q' flag if it is present
case "$rc_flags" in
*-q*) rc_flags_mod=`echo "${rc_flags}" | sed -Ee 's/(^-q | -q | -q$)//'` ;;
esac
if ! ${command} -t -q ${rc_flags_mod}; then
err 1 "`${command} -t ${rc_flags_mod}` Configuration file sanity check failed"
else sh /usr/local/bin/dhcpd-sync
fi
}
0 0 * * * root /usr/local/etc/rc.d/isc-dhcpd restart
В-третьих, для того, чтобы появились таблицы маршрутизации кроме нулевой, а также заработали «ядерный» nat и ipfw, нужно пересобрать ядро со следующими параметрами (конечно, возможны варианты, но они, опять же, по ссылкам в конце):
options IPFIREWALL
options IPFIREWALL_VERBOSE
options IPFIREWALL_VERBOSE_LIMIT=50
options IPFIREWALL_NAT
options LIBALIAS
options DUMMYNET
options HZ=1000
options ROUTETABLES=2
Для того, чтобы вторая таблица маршрутизации (под номером «1», т.к. у первой номер «0») работала после перезагрузки, необходимо создать в rc.d (у меня размещен в /usr/local/etc/rc.d/) файл со следующим содержимым:
#!/bin/sh
#
# PROVIDE: SETFIB1
# REQUIRE: NETWORKING
# BEFORE: DAEMON
#
# Add the following lines to /etc/rc.conf to enable setfib -1 at startup
# setfib1 (bool): Set to "NO" by default.
# Set it to "YES" to enable setfib1
# setfib1_defaultroute (str): Set to "" by default
# Set it to ip address of default gateway for use in fib 1
. /etc/rc.subr
name="setfib1"
rcvar=`set_rcvar`
load_rc_config $name
[ -z "$setfib1_enable" ] && setfib1_enable="NO"
[ -z "$setfib1_defaultrouter" ] && setfib1_defaultrouter=""
start_cmd="${name}_start"
stop_cmd="${name}_stop"
setfib1_start()
{
if [ ${setfib1_defaultrouter} ]
then
setfib 1 route add -net default ${setfib1_defaultrouter}
else
echo "Can not set default route for fib 1 - setfib1_defaultrouter is not assigned in rc.conf!"
fi
}
setfib1_stop()
{
setfib 1 route del -net default
}
run_rc_command "$1"
А также дописать в rc.conf несколько строчек, например для первичного «маршрутизатора»:
setfib1_enable="YES"
setfib1_defaultrouter="2.2.2.1"
По сути, данный загрузочный скрипт ни много ни мало добавляет во вторую таблицу маршрут по умолчанию. При необходимости можно запускать до 65536 таблиц маршрутизации (в 10 версии FreeBSD), копируя вышеописанный скрипт с незначительными изменениями и дописывая параметры в rc.conf. (Разумеется, в параметрах ядра необходимо сначала включить эти 65536 таблиц.)
Моя конфигурация rc.conf на основном «маршрутизаторе»:
Но сначала немного комментариев:
Eth0 – физический интерфейс основного внешнего канала.
Eth1 – физический интерфейс резервного внешнего канала.
Eth2 – физический интерфейс внутреннего канала.
Vlan1 – интерфейс основного внешнего канала.
Vlan2 – интерфейс резервного внешнего канала.
Vlan3 и vlan4 – зарезервированы под будущую функциональность, об этом в конце статьи.
10.0.0.1 – адрес «маршрутизатора» во внутренней сети, соответственно, у дублирующего будет, например 10.0.0.2.
1.1.1.2 и 1.1.1.1 – ip-адрес и шлюз по умолчанию для основного внешнего канала.
2.2.2.2 и 2.2.2.1 – ip-адрес и шлюз по умолчанию для резервного внешнего канала.
## ВНИМАНИЕ! Имена интерфейсов и ip-адреса взяты для примера, в каждом конкретном случае они будут свои! ##
hostname="SERVER1.companyname.local"
keymap="ru.koi8-r"
font8x8="cp866-8x8"
font8x14="cp866-8x14"
font8x16="cp866-8x16"
scrnmap="koi8-r2cp866"
cursor="destructive"
ifconfig_eth0="up"
vlans_eth0="vlan1 vlan3"
create_args_vlan1="vlan 1"
create_args_vlan3="vlan 3"
ifconfig_eth1="up"
vlans_eth1="vlan2 vlan4"
create_args_vlan2="vlan 2"
create_args_vlan4="vlan 4"
ifconfig_eth2="inet 10.0.0.1 netmask 255.255.255.0"
ifconfig_vlan1="inet 1.1.1.2/24"
ifconfig_vlan3="inet 10.0.1.1/30"
ifconfig_vlan2="inet 2.2.2.2/24"
ifconfig_vlan4="inet 10.0.2.1/30"
defaultrouter="1.1.1.1"
setfib1_enable="YES"
setfib1_defaultrouter="2.2.2.1"
gateway_enable="YES"
sshd_enable="YES"
moused_enable="YES"
ntpd_enable="YES"
powerd_enable="YES"
hald_enable="YES"
dbus_enable="YES"
dumpdev="AUTO"
firewall_enable="YES"
firewall_logging="YES"
firewall_script="/etc/firewall.sh"
named_enable="YES"
named_program="/usr/sbin/named"
named_flags="-u bind -c /etc/namedb/named.conf"
dhcpd_enable="YES"
dhcpd_conf="/usr/local/etc/dhcpd.conf"
dhcpd_ifaces="eth2"
Ниже привожу настройки NAT и Firewall, которые работают у меня:
При работе через основной внешний канал:
#!/bin/sh
# Delete all rules
/sbin/ipfw -q -f flush
/sbin/ipfw -q -f pipe flush
/sbin/ipfw -q -f queue flush
/sbin/ipfw -q -f nat 1 delete
/sbin/ipfw -q -f table all flush
# Parameters
ipfw="/sbin/ipfw -q add"
extM_if="vlan1"
extM_ip="1.1.1.2"
extS_if="vlan2"
extS_ip="2.2.2.2"
int_if="eth2"
int_ip="10.0.0.1"
lan_net="10.0.0.0/24"
odmin="10.0.0.111"
# Tables
# Table 1 - non-routes networks
/sbin/ipfw table 1 add 192.168.0.0/16
/sbin/ipfw table 1 add 172.16.0.0/12
/sbin/ipfw table 1 add 10.0.0.0/8
/sbin/ipfw table 1 add 127.0.0.0/8
/sbin/ipfw table 1 add 0.0.0.0/8
/sbin/ipfw table 1 add 169.254.0.0/16
/sbin/ipfw table 1 add 192.0.2.0/24
/sbin/ipfw table 1 add 204.152.64.0/23
/sbin/ipfw table 1 add 224.0.0.0/3
# Choose route table
$ipfw setfib 0 all from any to any via $int_if
# Allow all traffic on loopback
$ipfw allow all from any to any via lo0
# Deny access to lo0 from out
$ipfw deny log all from any to 127.0.0.0/8
# Deny outcome packets from lo0
$ipfw deny log all from 127.0.0.0/8 to any
# Allow returning
$ipfw check-state
# Deny IPv6
$ipfw deny log ipv6 from any to any
# Antispoofing
$ipfw deny log all from any to any not antispoof in
# Block any delayed packets (fragments)
$ipfw deny all from any to any frag
#########################################
# Internal interface, outcoming traffic #
#########################################
# Allow all traffic from gateway to lan
$ipfw allow all from any to $lan_net out via $int_if
# Deny and log other
$ipfw deny log all from any to any out via $int_if
########################################
# Internal interface, incoming traffic #
########################################
# Deny all Netbios
$ipfw deny tcp from any to any 81,137,138,139 in via $int_if
# Allow traffic on internal interface
# DHCP
$ipfw allow udp from any to me 67,68,1515,1516 in via $int_if
# Mail
$ipfw allow tcp from $lan_net to any 25,110,143,465,993,995 in via $int_if
# Time
$ipfw allow tcp from $lan_net to any 37 in via $int_if
$ipfw allow udp from $lan_net to any 123 in via $int_if
# ICQ
$ipfw allow tcp from $lan_net to any 443,5190,5222 in via $int_if
# FTP and some other
$ipfw allow tcp from $lan_net to any 21,22,49152-65535 in via $int_if
# HTTP
$ipfw allow tcp from $lan_net to any 80 in via $int_if
# Output whois
$ipfw allow tcp from $lan_net to any 43 in via $int_if
# DNS
$ipfw allow udp from $lan_net to any 53 in via $int_if
$ipfw allow tcp from $lan_net 53 to $int_ip in via $int_if
$ipfw allow tcp from $lan_net to $int_ip 53 in via $int_if
# Ping
$ipfw allow icmp from $lan_net to any icmptypes 0,3,8,11 in via $int_if
# For admin
$ipfw allow all from $odmin 1025-6000,11111,22222,50000-60000 to any in via $int_if
$ipfw allow all from 10.0.0.2 22 to $int_ip in via $int_if
$ipfw 55100 allow all from any to $int_ip 22 in via $int_if
# Deny and log other
$ipfw deny log all from any to any in via $int_if
#########################################
# External interface, outcoming traffic #
#########################################
# Deny all outcoming traffic to non-route networks
$ipfw deny log all from any to 'table(1)' out via $extM_if
$ipfw deny log all from any to 'table(1)' out via $extS_if
# Deny broadcast ICMP on ext interface
$ipfw deny icmp from any to 255.255.255.255 out via $extM_if
$ipfw deny icmp from any to 255.255.255.255 out via $extS_if
# Deny multicast on ext interface
$ipfw deny all from 224.0.0.0/4 to any out via $extM_if
$ipfw deny all from 224.0.0.0/4 to any out via $extS_if
# Allow me go to internet
$ipfw allow all from $extM_ip to any out via $extM_if setup keep-state
$ipfw allow all from $extS_ip to any out via $extS_if setup keep-state
# DNS BIND
$ipfw allow udp from $extM_ip to any 53 out via $extM_if keep-state
$ipfw allow udp from $extS_ip to any 53 out via $extS_if keep-state
# Time
$ipfw allow udp from $extM_ip to any 123 out via $extM_if keep-state
$ipfw allow tcp from $extM_ip to any 37 out via $extM_if setup keep-state
# Output whois
$ipfw allow tcp from $extM_ip to any 43 out via $extM_if setup keep-state
# NAT
/sbin/ipfw -q nat 1 config log if $extM_if reset same_ports deny_in unreg_only redirect_port tcp 10.0.0.111:33333 33333 redirect_port udp 10.0.0.111:11111 11111 redirect_port tcp 10.0.0.111:22222 22222 redirect_port udp 10.0.0.111:22222 22222
# NAT outcoming traffic
$ipfw nat 1 ip from any to any out via $extM_if
# Allow traffic on outcoming interface
# Mail
$ipfw allow tcp from any to any 25,110,143,465,993,995 out via $extM_if
# ICQ
$ipfw allow tcp from any to any 443,5190,5222 out via $extM_if
# FTP and some other
$ipfw allow tcp from any to any 21,22,49152-65535 out via $extM_if
# HTTP
$ipfw allow tcp from any to any 80 out via $extM_if
# Ping
$ipfw allow icmp from any to any icmptypes 0,3,8,11 out via $extM_if
$ipfw allow icmp from any to any icmptypes 0,3,8,11 out via $extS_if
# For admin
$ipfw allow tcp from any 1025-6000 to any out via $extM_if
$ipfw allow all from any 11111,22222,50000-60000 to any out via $extM_if
# Deny and log other
$ipfw deny log all from any to any out via $extM_if
$ipfw deny log all from any to any out via $extS_if
########################################
# External interface, incoming traffic #
########################################
# Deny all incoming traffic from non-route networks
$ipfw deny log all from 'table(1)' to any in via $extM_if
$ipfw deny log all from 'table(1)' to any in via $extS_if
# Deny ident
$ipfw deny tcp from any to any 113 in via $extM_if
$ipfw deny tcp from any to any 113 in via $extS_if
# Deny all Netbios
$ipfw deny tcp from any to any 81,137,138,139 in via $extM_if
$ipfw deny tcp from any to any 81,137,138,139 in via $extS_if
# SSH (also for internal network)
$ipfw allow all from any to me 22 in via $extM_if
$ipfw allow all from any to me 22 in via $extS_if
# NAT incoming traffic
$ipfw nat 1 ip from any to any in via $extM_if
# Allow traffic on outcoming interface
# Mail
$ipfw allow tcp from any 25,110,143,465,993,995 to any in via $extM_if
# ICQ
$ipfw allow tcp from any 443,5190,5222 to any in via $extM_if
# FTP and some other
$ipfw allow tcp from any 21,22,49152-65535 to any in via $extM_if
# HTTP
$ipfw allow tcp from any 80 to any in via $extM_if
# Ping
$ipfw allow icmp from any to any icmptypes 0,3,8,11 in via $extM_if
$ipfw allow icmp from any to any icmptypes 0,3,8,11 in via $extS_if
# For admin
$ipfw allow tcp from any to $odmin 1025-6000 in via $extM_if
$ipfw allow all from any to $odmin 11111,22222,50000-60000 in via $extM_if
# Deny and log other
$ipfw deny log all from any to any in via $extM_if
$ipfw deny log all from any to any in via $extS_if
$ipfw deny log all from any to any
При работе через резервный внешний канал все настройки те же, меняется только шапка:
# Parameters
ipfw="/sbin/ipfw -q add"
extM_if="vlan2"
extM_ip="2.2.2.2"
extS_if="vlan1"
extS_ip="1.1.1.1"
int_if="eth2"
int_ip="10.0.0.1"
lan_net="10.0.0.0/24"
odmin="10.0.0.111"
serv="10.0.0.4
Также на «маршрутизаторах» настроен sshguard, но искушенный читатель сможет сам найти и установить данную программу.
ToFoIn – Toggle Failover of Internet. Скорее всего, название более чем амбициозное, но характеристики продукта точнее имеющейся, я не придумал. Ниже размещен текст скриптов и сопутствующих файлов с небольшим пояснением.
## tofoin.conf ##
## by LordNicky v0.6 20140719 ##
## Little about the modules and about what function they perform.
## Tester - Testing the availability of the Internet on selected channel.
## Judge - Test results analysis, the decision to switch
## from one channel to another.
## Logger - Event logging.
## Watchdog - Testing and debugging of the scripts.
## Configuration.
## Amouth of the Internet channels.
CNUMBER=2
## Main Internet channel properties.
## Interface name.
EXT_0_IF=vlan10
## Id number of the routing table.
RTABLE_0=0
## Reserve Internet channel properties.
## Interface name.
EXT_1_IF=vlan20
## Id number of the routing table
RTABLE_1=1
## URL's supposed to be used for diagnostic of the availability
## of the Internet channel. PTARGET_0 should be domain name, and
## PTARGET_1 should be IP address.
## Attention: The resources should be different.
PTARGET_0=ya.ru
PTARGET_1=8.8.8.8
## Count of icmp packets used for testing one resource.
PNUMBER=2
## Period of launching of the module "Tester" (in seconds).
## Strongly not recomended to set a value less than 60.
TESTERPERIOD=240
## Period of launching of the module "Judge" (in seconds).
## Strongly not recomended to set a value less than TESTERPERIOD.
## Usually enough TESTERPERIOD + 60.
JUDGEPERIOD=300
## Launching sensitivity for the modules Tester and Judge.
## Usually enough 60.
SENSITIVITY=60
## The maximum operating time for the module Tester.
TESTERMAXDELAY=40
## The maximum operating time for the module Judge.
JUDGEMAXDELAY=30
## The maximum operating time for the module Logger.
LOGGERMAXDELAY=20
## Amount of tests that successfully passed before returning
## to the main channel. Thereby, time elapsed since the restore
## the work main channel is approximately (WNUMBER+1)*JUDGEPERIOD
## seconds.
WNUMBER=3
## The frequency of writing error message into the log file.
## The main idea is the following. At first time the message
## is written completely. After LOGFREQ1 repetitions logger
## writes the only message about LOGFREQ1 the same messages.
## Later in each LOGFREQ2 repetitions logger writes the only
## message about LOGFREQ2 the same messages. This algorithm
## works only if the same messages are following after each other.
LOGFREQ1=5
LOGFREQ2=20
## File paths.
## Paths for configuration script files IPFW.
## Default file. (It is written in the rc.conf)
FIRESETDEF=/etc/firewall.sh
## Settings for main Internet channel.
FIRESET_0=/etc/rules.firewall0
## Settings for reserve Internet channel.
FIRESET_1=/etc/rules.firewall1
## Paths for all ToFoIn files.
## Daemon.
DAEMON=/path/to/file/tofoin_daemon.sh
## Tester.
TESTER=/path/to/file/tofoin_tester.sh
## Judge.
JUDGE=/path/to/file/tofoin_judge.sh
## Logger.
LOGGER=/path/to/file/tofoin_logger.sh
## Watchdog.
WATCHDOG=/path/to/file/tofoin_watchdog.sh
## Log file. It is recommended to locate it into the /var/log.
LOGFILE=/path/to/file/tofoin.log
## The directory supposed for test results. It is recomended
## to locate it into the /tmp.
TESTER_RESULT=/path/to/directory
## Auxiliary module file Judge. It is recommended to locate
## it into the /tmp.
JUDGEMETER=/path/to/file/judgemeter
## Auxiliary module file Logger. It is recommended to locate
## it into the /tmp.
LOGTMP=/path/to/file/logger.tmp
LOGMETER=/path/to/file/logmeter
## PID files for all executable modules. It is recommended
## to locate it into /var/run.
DAEMON_PID=/path/to/file/tofoin_daemon.pid
TESTER_PID=/path/to/directory
JUDGE_PID=/path/to/file/tofoin_judge.pid
LOGGER_PID=/path/to/file/tofoin_logger.pid
WATCHDOG_PID=/path/to/file/tofoin_watchdog.pid
#!/usr/local/bin/bash
# by LordNicky v0.5 20140717
. /root/ToFoIn/tofoin.conf
test_time=`date +%s`;
judge_time=`date +%s`;
echo $$ > $DAEMON_PID;
$LOGGER "DAEMON: start successfully with pid $$" &
tester_0="$TESTER $RTABLE_0 10 0";
tester_1="$TESTER $RTABLE_1 10 1";
$tester_0 & $tester_1 &
while true
do
current_time=`date +%s`;
if [ "`expr $current_time - $test_time`" -ge "$TESTERPERIOD" ]
then $tester_0 & $tester_1 & test_time=`date +%s`;
else :;
fi
if [ "`expr $current_time - $judge_time`" -ge "$JUDGEPERIOD" ]
then $JUDGE & judge_time=`date +%s`;
else :;
fi
sleep $SENSITIVITY;
done
#!/usr/local/bin/bash
# by LordNicky v0.7 20140717
. /root/ToFoIn/tofoin.conf
exit_function () {
rm $tester_pid;
exit $exit_code;
}
tester_pid=$TESTER_PID/tofoin_test_$3.pid;
if [ -e $tester_pid ];
then $WATCHDOG "tofoin_test" "$tester_pid" "$3" & exit 0;
else echo `date +%s` $$ > $tester_pid;
if [ "$2" -eq 10 ];
then if setfib $1 ping -c $PNUMBER $PTARGET_0 > /dev/null;
then echo `date +%s` "0 0" > $TESTER_RESULT/result_$3;
exit_code=0; exit_function;
else if setfib $1 ping -c $PNUMBER $PTARGET_1 > /dev/null;
then echo `date +%s` "0 1" > $TESTER_RESULT/result_$3;
exit_code=0; exit_function;
else echo `date +%s` "1 1" > $TESTER_RESULT/result_$3;
exit_code=0; exit_function;
fi
fi
elif [ "$2" -eq 0 ];
then setfib $1 ping -c $PNUMBER $PTARGET_0;
exit_code=0; exit_function;
elif [ "$2" -eq 1 ];
then setfib $1 ping -c $PNUMBER $PTARGET_1;
exit_code=0; exit_function;
else setfib $1 ping -c $PNUMBER $2;
exit_code=1; exit_function;
fi
fi
Как и было упомянуто ранее, у модуля tester несколько расширена функциональность для ручного запуска. В разделе «решение» описано, каким образом. Также, как видно из текста скрипта, tester записывает результаты в файл только в случае штатного запуска.
#!/usr/local/bin/bash
# by LordNicky v0.7 20140717
. /root/ToFoIn/tofoin.conf
exit_function () {
rm $JUDGE_PID;
exit $exit_code;
}
decision_function () {
if [ "$actualchan" -eq "$prefchan" ];
then if [ "$actualchan" -eq 0 ];
then $LOGGER "JUDGE: No problems detected" &
exit_code=0; exit_function;
elif [ "$actualchan" -eq 1 ];
then echo -e "0" > $JUDGEMETER;
$LOGGER "JUDGE: No problems detected at channel $actualchan" &
exit_code=0; exit_function;
else $LOGGER "JUDGE(decision): Invalid actualchan = $actualchan" &
exit_code=1; exit_function;
fi
else if [ "$prefchan" -eq 1 ];
then switch_function; exit_code=0; exit_function;
elif [ "$prefchan" -eq 0 ];
then if [ "$actualstate" -eq 0 ]
then meter=`cat $JUDGEMETER`;
if [ "$meter" -eq "$WNUMBER" ];
then switch_function; exit_code=0; exit_function;
elif [ "$meter" -lt "$WNUMBER" ];
then expr $meter + 1 > $JUDGEMETER;
exit_code=0; exit_function;
else echo -e "0" > $JUDGEMETER; exit_code=0; exit_function;
fi
elif [ "$actualstate" -eq 1 ]
then $LOGGER "JUDGE: Emergency switch to $prefchan";
switch_function; exit_code=0; exit_function;
else $LOGGER "JUDGE(decision): Invalid actualstate = $actualstate" & exit_code=1; exit_function;
fi
else $LOGGER "JUDGE(decision): Invalid prefchan = $prefchan" &
exit_code=1; exit_function;
fi
fi
}
switch_function () {
echo -e "0" > $JUDGEMETER;
if [ "$prefchan" -eq 0 ];
then /etc/rc.d/named stop;
cp $FIRESET_0 $FIRESETDEF;
/etc/rc.d/ipfw restart;
setfib $RTABLE_0 /etc/rc.d/named start;
$LOGGER "JUDGE: Now switching on channel $RTABLE_0" &
exit_code=0; exit_function;
elif [ "$prefchan" -eq 1 ]
then /etc/rc.d/named stop;
cp $FIRESET_1 $FIRESETDEF;
/etc/rc.d/ipfw restart;
setfib $RTABLE_1 /etc/rc.d/named start;
$LOGGER "JUDGE: Now switching on channel $RTABLE_1" &
exit_code=0; exit_function;
else $LOGGER "JUDGE(switch): Invalid prefchan = $prefchan" &
exit_code=1; exit_function;
fi
}
createarea_function () {
for ((a=0; a < CNUMBER ; a++))
do
current_time=`date +%s`
timearea[$a]=`cut -c 1-10 $TESTER_RESULT/result_$a`;
if [ "`expr $current_time - ${timearea[$a]}`" -ge 0 ];
then if [ "`expr $current_time - ${timearea[$a]}`" -lt "`expr $TESTERPERIOD + 120`" ];
then :;
else $LOGGER "JUDGE: MAX period" &
$WATCHDOG &
exit_code=1; exit_function;
fi
else $LOGGER "JUDGE: testmodule $a in future" &
$WATCHDOG &
exit_code=1; exit_function;
fi
statearea[$a]=`cut -c 12 $TESTER_RESULT/result_$a`;
if [ "$actualchan" -eq "$a" ]
then actualstate=${statearea[$a]};
else :;
fi
done
}
findarea_function () {
for ((a=0; a < CNUMBER ; a++))
do
if [ "${statearea[$a]}" -eq 0 ]
then prefchan=$a; decision_function;
exit_code=0; exit_function;
else if [ "${statearea[$a]}" -eq 1 ]
then continue
else $LOGGER "JUDGE: Invalid channel state" &
exit_code=1; exit_function;
fi
fi
done
}
if [ -e $JUDGE_PID ]
then $WATCHDOG "tofoin_judge" "$JUDGE_PID" & exit 0;
else echo `date +%s` $$ > $JUDGE_PID;
if ipfw list | grep nat | egrep -q $EXT_0_IF;
then actualchan=0;
elif ipfw list | grep nat | egrep -q $EXT_1_IF;
then actualchan=1;
else $LOGGER "JUDGE: NAT error" &
prefchan=0; switch_function;
exit_code=1; exit_function;
fi
createarea_function;
findarea_function;
$LOGGER "JUDGE: All channels down" &
exit_code=1; exit_function;
fi
В модуле judge оставлены места под дальнейшее улучшение, но в целом никаких излишеств.
#!/usr/local/bin/bash
# by LordNicky v0.5 20140713
. /root/ToFoIn/tofoin.conf
exit_function () {
rm $LOGGER_PID;
exit $exit_code;
}
main_function () {
if [[ `tail -n 1 $LOGFILE | grep -o "$1" | grep -o "JUDGE: No problems detected"` = "JUDGE: No problems detected" ]];
then exit_code=0; exit_function;
else if [[ `cat $LOGTMP` = $1 ]];
then meter=`cat $LOGMETER`;
if [ "$meter" -ge "$LOGFREQ2" ];
then echo -e "0" > $LOGMETER;
echo -e "`date -j +%Y%m%d%H%M` last message repeat $LOGFREQ2 times" >> $LOGFILE;
exit_code=0; exit_function;
elif [ "$meter" -ge "$LOGFREQ1" ];
then if [[ `tail -n 1 $LOGFILE | grep -o "last message repeat $LOGFREQ1 times"` = "last message repeat $LOGFREQ1 times" ]];
then expr $meter + 1 > $LOGMETER;
exit_code=0; exit_function;
elif [[ `tail -n 1 $LOGFILE | grep -o "last message repeat $LOGFREQ2 times"` = "last message repeat $LOGFREQ2 times" ]];
then expr $meter + 1 > $LOGMETER;
exit_code=0; exit_function;
else echo -e "`date -j +%Y%m%d%H%M` last message repeat $LOGFREQ1 times" >> $LOGFILE;
exit_code=0; exit_function;
fi
elif [ "$meter" -ge 0 ];
then expr $meter + 1 > $LOGMETER;
exit_code=0; exit_function;
else echo -e "0" > $LOGMETER;
echo -e "`date -j +%Y%m%d%H%M` LOGGER: logmeter index error, write 0" >> $LOGFILE;
exit_code=1; exit_function;
fi
else if [ `cat $LOGMETER` -eq 0 ];
then echo -e "$1" > $LOGTMP;
echo -e "`date -j +%Y%m%d%H%M` $1" >> $LOGFILE;
exit_code=0; exit_function;
else echo -e "0" > $LOGMETER;
echo -e "$1" > $LOGTMP;
echo -e "`date -j +%Y%m%d%H%M` $1 ; LOGMETER now zero" >> $LOGFILE;
exit_code=0; exit_function;
fi
fi
fi
}
if [ -e $LOGGER_PID ];
then sleep $((RANDOM%5+1));
if [ -e $LOGGER_PID ];
then $WATCHDOG "tofoin_logger" "$LOGGER_PID" & exit 0;
else echo `date +%s` $$ > $LOGGER_PID;
main_function "$1";
fi
else echo `date +%s` $$ > $LOGGER_PID;
main_function "$1";
fi
Самый, на мой взгляд, страшный модуль с точки зрения восприятия – это logger. Но, к сожалению, проще написать не получилось. В основном, большая часть скрипта посвящена предотвращению появления повторяющихся сообщений, откуда и кажущаяся сложность.
#!/usr/local/bin/bash
# by LordNicky v0.5 20140713
. /root/ToFoIn/tofoin.conf
exit_function () {
rm $WATCHDOG_PID;
exit $exit_code;
}
kill_function () {
if [[ "`ps -o command -p $proc_pid | grep -o "$proc_name"`" = "$proc_name" ]];
then $LOGGER "WATCHDOG: Other $proc_s_name working during $diff, kill him" &
kill $proc_pid;
else $LOGGER "WATCHDOG: None or other process on $proc_s_name pid, cleaning pid file" &
fi
if [[ "$proc_name" = "tofoin_watchdog" ]];
then main_function;
else rm $proc_pid_file;
fi
}
main_function () {
echo `date +%s` $$ > $WATCHDOG_PID;
proc_name=${one:-all};
return_wait=10
if [[ "$proc_name" = "all" ]];
b=0; c=0
then for ((a=0; a < CNUMBER ; a++))
do
current_time=`date +%s`;
tester_result=$TESTER_RESULT/result_$a;
tester_time=`cut -c 1-10 $tester_result`;
diff=`expr $current_time - $tester_time`;
if [ "$diff" -ge 0 ]
then if [ "$diff" -lt "`expr $TESTERPERIOD + 120`" ];
then :;
else proc_name=tofoin_daemon; proc_pid=`cat $DAEMON_PID`;
if [[ "`ps -o command -p $proc_pid | grep -o "$proc_name"`" = "$proc_name" ]];
then $LOGGER "WATCHDOG: Restart daemon" &
kill $proc_pid; $DAEMON &
else $LOGGER "WATCHDOG: None daemon process, start" &
$DAEMON &
fi
exit_code=0; exit_function;
fi
else $LOGGER "WATCHDOG: Check date" &
fi
done
elif [[ "$proc_name" = "tofoin_test" ]];
then proc_pid_file=$two; cnumber=$three;
test_function; return_val=$?;
if [[ "$return_val" = "$return_wait" ]];
then sleep $TESTERMAXDELAY; test_function "nowait";
else :;
fi
elif [[ "$proc_name" = "tofoin_judge" ]];
then proc_pid_file=$JUDGE_PID;
judge_function; return_val=$?;
if [[ "$return_val" = "$return_wait" ]];
then sleep $JUDGEMAXDELAY; judge_function "nowait";
else :;
fi
elif [[ "$proc_name" = "tofoin_logger" ]];
then proc_pid_file=$LOGGER_PID;
logger_function; return_val=$?;
if [[ "$return_val" = "$return_wait" ]];
then sleep $LOGGERMAXDELAY; logger_function "nowait";
else :;
fi
else $LOGGER "WATCHDOG: Incorrect process name";
fi
exit_code=0; exit_function;
}
test_function () {
if [ -e $proc_pid_file ];
then proc_pid=`cut -c 12-18 $proc_pid_file`;
proc_s_name="tester $cnumber";
start_time=`cut -c 1-10 $proc_pid_file`;
current_time=`date +%s`;
diff=`expr $current_time - $start_time`;
if [ "$diff" -ge 0 ];
then if [ "$diff" -lt "$TESTERMAXDELAY" ];
then if [[ "$1" = "nowait" ]];
then if [ "$proc_pid" = "$proc_temp_pid" ];
then kill_function; return 0;
else $LOGGER "WATCHDOG: Pid of $proc_s_name was changed, exit" &
fi
else $LOGGER "WATCHDOG: $proc_s_name now working, try wait" &
proc_temp_pid=$proc_pid;
return $return_wait;
fi
else kill_function; return 0;
fi
else $LOGGER "WATCHDOG: Time error in $proc_s_name = $diff" &
kill_function; return 0;
fi
else return 0;
fi
}
judge_function () {
if [ -e $proc_pid_file ];
then proc_pid=`cut -c 12-18 $proc_pid_file`;
proc_s_name="judge";
start_time=`cut -c 1-10 $proc_pid_file`;
current_time=`date +%s`;
diff=`expr $current_time - $start_time`;
if [ "$diff" -ge 0 ];
then if [ "$diff" -lt "$JUDGEMAXDELAY" ];
then if [[ "$1" = "nowait" ]];
then if [ "$proc_pid" = "$proc_temp_pid" ];
then kill_function; return 0;
else $LOGGER "WATCHDOG: Pid of $proc_s_name was changed, exit" &
fi
else $LOGGER "WATCHDOG: $proc_s_name now working, try wait" &
proc_temp_pid=$proc_pid;
return $return_wait;
fi
else kill_function; return 0;
fi
else $LOGGER "WATCHDOG: Time error in $proc_s_name = $diff" &
kill_function; return 0;
fi
else return 0;
fi
}
logger_function () {
if [ -e $proc_pid_file ];
then proc_pid=`cut -c 12-18 $proc_pid_file`;
proc_s_name="logger";
start_time=`cut -c 1-10 $proc_pid_file`;
current_time=`date +%s`;
diff=`expr $current_time - $start_time`;
if [ "$diff" -ge 0 ];
then if [ "$diff" -lt "$LOGGERMAXDELAY" ];
then if [[ "$1" = "nowait" ]];
then if [ "$proc_pid" = "$proc_temp_pid" ];
then kill_function; return 0;
else echo -e "`date -j +%Y%m%d%H%M` WATCHDOG: Pid of $proc_s_name was changed, exit" >> $LOGFILE;
fi
else echo -e "`date -j +%Y%m%d%H%M` WATCHDOG: $proc_s_name now working, try wait" >> $LOGFILE;
proc_temp_pid=$proc_pid;
return $return_wait;
fi
else kill_function; return 0;
fi
else echo -e "`date -j +%Y%m%d%H%M` WATCHDOG: Time error in $proc_s_name = $diff" >> $LOGFILE;
kill_function; return 0;
fi
else return 0;
fi
}
one=$1;
two=$2;
three=$3;
if [ -e $WATCHDOG_PID ];
then proc_pid=`cut -c 12-18 $WATCHDOG_PID`;
proc_name="tofoin_watchdog";
proc_s_name="watchdog";
start_time=`cut -c 1-10 $WATCHDOG_PID`;
current_time=`date +%s`;
diff=`expr $current_time - $start_time`;
if [ "$diff" -ge 0 ];
then if [ "$diff" -lt "`expr $TESTERMAXDELAY + $JUDGEMAXDELAY + $LOGGERMAXDELAY + 30`" ];
then $LOGGER "WATCHDOG: Other $proc_s_name already working, exit" & exit 0;
else kill_function;
fi
else $LOGGER "WATCHDOG: Time error in $proc_s_name = $diff" &
kill_function;
fi
else main_function;
fi
Watchdog – самый большой и, пожалуй, неоднозначный скрипт из всех представленных. Получился он таким, поскольку предпринималась попытка предусмотреть все возможные варианты сбоев. Но пока что так. Поскольку запуск данного модуля предполагается с помощью cron, в /etc/crontab следует внести что-то, вроде:
0 * * * * root /path/to/file/tofoin_watchdog.sh
Скрипт прошел тестирование в течение шести месяцев. При этом критичных ошибок найдено не было, мелкие исправлены. Все модули работают по заданному алгоритму без отклонений и непредсказуемых действий. Файл журнала событий достаточно информативен и позволяет судить о возникших проблемах и времени их возникновения и решения. Таким образом, можно заключить, что первоначальная цель достигнута, дальнейшие планы развития изложены ниже.
В планах дальнейшего развития скрипта:
Разумеется, при написании, а в особенности после, возникло множество вопросов. Самый главный из них такой:
Имеются следующие переменные:
a =<сами устанавливаем значение>
HI_1=”123”
HI_2=”321”
Нужно вызвать переменные HI_1 и HI_2, изменяя только a, т.е. вызов будет выглядеть как-то так:
${HI_$a} ## это заведомо неверное выражение дано здесь только для примера
И, в случае, если мы заранее установили a=1, данное выражение будет означать 123, а если a=2, то 321. Я изучил литературу по bash, которая, по моим представлениям, должна давать ответ на данный вопрос, но, к сожалению, не нашел, как это сделать. Использование данной функции сильно упростило бы скрипт и позволило бы его легко расширить.
В остальном, конечно, вопросы общего характера, — насколько актуально данное решение? Какие ошибки допущены в скрипте? Как лучше всего решить вопросы, обозначенные в планах и по тексту статьи? Ваши комментарии?
Если есть желание помочь в усовершенствовании, то пишите в личные сообщения, обсудим возможное сотрудничество.
Также при настройке системы и написании скрипта использовались многие другие материалы с opennet.ru, lissyara.su, habrahabr.ru и многих других сайтов. К сожалению, многие ссылки со временем были утеряны, поэтому, если вы найдете здесь фрагменты откуда-либо, то я с удовольствием добавлю на них ссылки. Отдельная благодарность Алексею Ересько и Валерию Друба за консультации и помощь в решении сложностей в процессе подготовки и написания скрипта, а также Олегу Матусевичу за помощь в подготовке статьи.
З.Ы. При использовании материалов данной статьи обязательно указывать ссылку на источник и автора.
Автор: LordNicky
Источник [18]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/freebsd/72834
Ссылки в тексте:
[1] Ли К., Альбитц П. — DNS и BIND (5-е издание): http://rutracker.org/forum/viewtopic.php?t=2302828
[2] DNS сервер BIND: http://www.bog.pp.ru/work/bind.html
[3] Failover DHCP: http://bos.avenue.com.ua/freebsd/failover-dhcp.html
[4] DDNS+DHCP: http://www.lissyara.su/articles/freebsd/programms/dns+dhcp/
[5] Multiple default routes in FreeBSD without BGP or similar: http://wiki.stocksy.co.uk/wiki/Multiple_default_routes_in_FreeBSD_without_BGP_or_similar
[6] setfib и переключение между таблицами маршрутизации: http://forum.lissyara.su/viewtopic.php?f=8&t=22518
[7] FreeBSD два провайдера. setfib: http://i-rrv.ru/freebsd-%D0%B4%D0%B2%D0%B0-%D0%BF%D1%80%D0%BE%D0%B2%D0%B0%D0%B9%D0%B4%D0%B5%D1%80%D0%B0-setfib/
[8] Подробное руководство по ipfw nat: http://forum.lissyara.su/viewtopic.php?f=4&t=18967&hilit=one_pass&start=725
[9] FreeBSD 9 + ipfw + ipfw nat: http://forum.lissyara.su/viewtopic.php?f=4&t=40131
[10] Подробное руководство по ipfw nat: http://www.lissyara.su/articles/freebsd/tuning/ipfw_nat/
[11] DUMMYNET: http://ipfw.ism.kiev.ua/dummynet.html
[12] Kernel NAT: http://alexnettm.org.ua/freebsd-nastroyka-kernel-nat/
[13] Быстрый тур по установке и настройке SSH: http://www.opennet.ru/base/sec/lavr-ssh.1.txt.html
[14] Защита SSH сервера: http://linuxopen.ru/2008/07/01/zashhita-ssh-servera.html
[15] Настройка сервера SSH (теория и практика): http://www.linuxcenter.ru/lib/articles/soft/ssh.phtml
[16] Основы BASH. Часть 2.: http://habrahabr.ru/post/52871/
[17] Advanced Bash-Scripting Guide: http://www.opennet.ru/docs/RUS/bash_scripting_guide/bash_scripting_guide-prog.html.gz#index_html
[18] Источник: http://habrahabr.ru/post/241654/
Нажмите здесь для печати.