ToFoIn – Toggle Failover of Internet или переключение между двумя внешними каналами в FreeBSD

Аннотация


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

Введение


Для повышения стабильности подключения к сети Интернет корпоративные решения подразумевают использование двух и более внешних сетевых каналов. Их одновременное (например, методом балансировки) или поочередное (с переключением между каналами) использование является не совсем тривиальной, однако уже решенной множеством способов задачей. Вот некоторые из них:

  1. Маршрутизаторы класса SOHO с двумя выходами во внешнюю сеть (здесь и далее по тексту под внешней сетью подразумевается Интернет, под внутренней – локальная сеть предприятия);
  2. Коммутаторы Layer 3, как правило, операторского класса, имеющие большое количество варьируемых параметров, в частности, позволяющих решить вышеописанную проблему;
  3. Множество самописных скриптов на разных языках для различных unix- и linux-подобных систем, чаще всего, сомнительного качества;
  4. Балансировка каналов правилами NAT;
  5. Балансировка или переключение с помощью proxy-сервера.

Каждый из вышеперечисленных подходов имеет свои достоинства и недостатки. Вариант первый, SOHO-маршрутизаторы:

Достоинства:
  • низкая цена;
  • простота установки и настройки.

Недостатки:
  • недостаточная надежность для корпоративного сегмента ввиду отсутствия резервирования;
  • отсутствие гибкости настройки, низкая функциональность. (Обычно подобные устройства умеют решать весьма ограниченный круг задач и «шаг в сторону» либо делать вовсе не умеют, либо это связано с различными сложностями.)

Второй вариант, коммутаторы Layer 3:

Достоинства:
  • надежность;
  • гибкость настройки;

Недостатки:
  • цена (Обычно цены на подобные устройства лежат за пределами 50 т. р.);
  • сложность настройки (устройство профессионального уровня требует соответствующего подхода).

Третий вариант, скрипты переключения:

Достоинства:
  • цена (бесплатно, не считая рабочего времени на настройку).

Недостатки:
  • непредсказуемая надежность (поскольку зачастую неизвестен профессиональный уровень авторов этих скриптов, без детального изучения сложно сделать вывод о качестве продукта);
  • отсутствие гибкости и сложность настройки (обычно подобные скрипты создаются для конкретных условий, и порой проще написать свою версию, нежели разбираться в чужой, что и объясняет подобное многообразие).

Четвертый вариант, балансировка правилами NAT:

Достоинства:
  • цена (бесплатно, не считая рабочего времени на настройку);
  • относительная простота настройки.

Недостатки:
  • необходимо иметь приблизительно равноценные по пропускной способности каналы.

Имеются сомнения относительно скорости работы в случае «падения» одного из внешних каналов.

И, наконец, пятый вариант, использование proxy-сервера:

Достоинства:
  • цена (бесплатно, не считая рабочего времени на настройку);
  • гибкость настройки.

Недостатки:
  • замедление потока данных;
  • необходимость дополнительной настройки на пользовательских машинах;
  • сложность настройки в нестандартных ситуациях.

В начале разработки, несколько лет назад, был выбран вариант написания собственного скрипта по следующим причинам. Во-первых, цена. По этому критерию отпадают коммутаторы Layer 3 из второго пункта. В условиях локальной сети на 10 машин решения корпоративного уровня – непозволительная роскошь. Об устройствах из первого пункта автор в момент принятия решения, увы, не знал. Кстати говоря, сейчас они не подходят уже по пункту «стабильность». А решение из четвертого пункта не подходит, т.к. имеющиеся интернет-каналы различаются в десятки раз по скорости и использование подобной схемы, на мой взгляд, не обосновано. Кроме того, добавляются сомнения относительно качества связи с внешней сетью в случае «падения» одного из каналов. Пятый же пункт не устраивает, во-первых, замедлением скорости потока, во-вторых – хотелось бы иметь независимое от необязательных компонентов решение. Соответственно, оставался пункт 3, где после исследования чужих скриптов и попыток их адаптировать, было решено отказаться от этой идеи и написать свой скрипт.

Со временем рядом с основным «маршрутизатором» на FreeBSD был установлен резервный, настройки dns, dhcp, nat и ipfw не раз претерпевали изменения. Всё постепенно развивалось и улучшалось, кроме вышеупомянутого скрипта, который в итоге было решено переписать, используя, как основополагающие, следующие принципы: модульность, единый файл настроек, а также гибкость и простоту настройки в любой unix-подобной системе, а также простоту добавления новых модулей.

Цели и задачи


Какова же конечная цель данного проекта? Создать универсальный и легко масштабируемый программный комплекс на основе системы клиент-сервер (хотя правильнее было бы назвать ее агент-сервер), ориентированный на определение неполадок во внешних и внутренних соединениях и автоматическое переключение на работоспособные соединения. В качестве агента в данном случае выступает «сборщик» информации о состоянии внешних и внутренних соединений на текущий момент времени, а в качестве сервера – часть программы, принимающая решение о том, какое соединение приоритетно и при необходимости отдающая команды на переключение на это соединение. При этом на сервере (в данном контексте) агент может не функционировать.

Итак:

  • Мы имеем n «маршрутизаторов» с m внешних каналов на каждом. При этом все n «маршрутизаторов» находятся в строгой иерархии.
  • На всех машинах независимо друг от друга работает агент, задача которого – собирать и «складывать» результаты тестирования внешних каналов на сервер или «маршрутизатор» с наивысшим на текущий момент приоритетом (предполагается, что серверная часть будет являться обязательным дополнением к агенту, в то время как агент не обязателен для исполнения серверных функций), а также определять его(сервера) доступность.
  • Сервер, в свою очередь, анализирует полученные данные и определяет, какой канал и на каком «маршрутизаторе» на текущий момент приоритетен. Именно для этого в статье рассмотрены настройки DHCP сервера, т.к. для изменения шлюза будут меняться настройки dhcpd.
  • В случае выхода из строя сервера на всех агентах активируется программа, которая выбирает и назначает новый сервер из числа агентов по заранее расставленным приоритетам и делегирует ей функции сбора информации о текущем состоянии внешних подключений и принятия решений о переключении. После восстановления работоспособности изначального сервера происходит обратный процесс — автоматическое переключение на него.

Подробности алгоритма можно расписывать очень долго, выше лишь изложена общая суть. Не спорю, что и n, и m (из примера выше) принимают значения больше 2-х крайне редко, но встречаются, поэтому, почему бы не сделать универсальное средство?

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

Решение


В силу многих причин было решено использовать в качестве основы локальной сети, а также шлюза в интернет старую машину(Pentium 3, 512 ОП) с FreeBSD, на текущий момент версии 9.2. Впоследствии для повышения надежности была установлена вторая подобная машина, которая работает в паре с уже имеющейся. Кстати говоря, за прошедшие два года поломок было ровно две – в первый раз вышел из строя БП, во второй – одна из сетевых карт. Стоит учесть, что при этом вся локальная сеть работала без нареканий, так как в случае сбоя вступала в игру дублирующая машина. Так что использование старого железа в данной схеме практически не сказывается на стабильности работы сети. Также имеется 2 внешних канала от разных интернет-провайдеров. Общая схема приведена ниже, на ней:

Синие и красные стрелки – внешние каналы связи.
Черные стрелки – внутренние каналы связи.

Выглядит данная система так:

image

Коммутатор разделяет трафик от провайдеров с помощью 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. Алгоритм его работы в общих чертах таков:
  1. На основе текущих правил ipfw определяется нынешний внешний канал.
  2. В цикле составляется массив актуальных данных состояния внешних каналов.
  3. Следующим циклом определяется предпочтительный внешний канал.
  4. Далее запускается функция определения, нужно ли переключать канал, и, если нужно, запускается функция переключения, которой передается внутренний номер канала для переключения. (Возврат на основной канал происходит не сразу. Это сделано для того, чтобы в случае нестабильной работы основного канала не происходило скачков туда-обратно, а переключение произошло только когда основной внешний канал станет работать стабильно).
  5. В конце, если возникла необходимость, запускается функция переключения, которая подставляет нужные настройки ipfw, перезапускает его, а также перезапускает с нужной таблицей маршрутизации Bind.

Разумеется, все ключевые действия записываются в журнал событий, а в случае возникновения нештатной ситуации, опять же, записывается причина ошибки и вызывается 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 по ссылке будет недоступна (а в последние несколько месяцев ситуация именно такая), приведу здесь скрипт для синхронизации настроек, а также ключевые моменты по настройке.
Настройка failover dhcpd
Для того, чтобы настроить failover dhcp peer нужно:
  1. Создать в /usr/local/etc основной файл настроек dhcpd.conf, на который ссылаемся в rc.conf. У меня выглядит следующим образом:
    /usr/local/etc/dhcpd.conf
    
    # 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.
  2. Создать папку /usr/local/etc/dhcpd. Создать в ней следующие файлы, содержащие примерно следующее:
    /usr/local/etc/dhcpd/dhcpd.conf_primary
    
    ##########################
    # 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;
    }
    

    /usr/local/etc/dhcpd/dhcpd.subnet
    
    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.
    /usr/local/etc/dhcpd/dhcpd.static
    
    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;
    }
    

    Данный файл, как нетрудно догадаться, для статических адресов.
  3. На втором «маршрутизаторе» файлы выглядят следующим образом:
    /usr/local/etc/dhcpd.conf
    
    # 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";
    

    /usr/local/etc/dhcpd/dhcpd.conf_secondary
    
    ###########################
    # 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 (о том, как именно – ниже).
  4. Создать исполняемый файл со следующим содержимым:
    /usr/local/bin/dhcpd-sync
    
    #!/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
    
  5. Создать пользователя dhcp-updater с соответствующими правами на обоих серверах, прописать его в настройках sudo, настроить подключение по ssh по ключу с основного на вторичный «маршрутизатор», удалить пароль. Возможно нужно будет еще создать папку /var/dhcp-backup/ на обоих машинах.
  6. Изменить кусок файла /usr/local/etc/rc.d/isc-dhcpd следующим образом:
    До:
    
    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
    }
    

  7. Если все настройки произведены правильно, при перезапуске dhcp-сервера на основной машине текущая конфигурация будет архивироваться, синхронизироваться со вторым сервером, и перезапуск будет происходить на обеих машинах.
  8. Нелишним было бы добавить в crontab следующее задание:
    0	0	*	*	*	root	/usr/local/etc/rc.d/isc-dhcpd restart
  9. На этом настройка failover dhcpd закончена.


В-третьих, для того, чтобы появились таблицы маршрутизации кроме нулевой, а также заработали «ядерный» 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/) файл со следующим содержимым:
/usr/local/etc/rc.d/setfib1

#!/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-адреса взяты для примера, в каждом конкретном случае они будут свои! ##
/etc/rc.conf

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, которые работают у меня:

При работе через основной внешний канал:
/etc/rules.firewall0

#!/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

При работе через резервный внешний канал все настройки те же, меняется только шапка:
/etc/rules.firewall1 шапка

# 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

##         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

tofoin_daemon.sh

#!/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  	

tofoin_tester.sh

#!/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 записывает результаты в файл только в случае штатного запуска.
tofoin_judge.sh

#!/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 оставлены места под дальнейшее улучшение, но в целом никаких излишеств.
tofoin_logger.sh

#!/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. Но, к сожалению, проще написать не получилось. В основном, большая часть скрипта посвящена предотвращению появления повторяющихся сообщений, откуда и кажущаяся сложность.
tofoin_watchdog.sh

#!/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


Итог


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

Планы


В планах дальнейшего развития скрипта:
  1. Разместить файлы по соответствующим системным каталогам;
  2. Рассмотреть необходимость запуска специальным пользователем с использованием sudo для определенных задач. В случае положительного решения адаптировать скрипт;
  3. Дописать модуль связи с zabbix;
  4. Сделать систему клиент-сервер. Именно под данную систему были настроены vlan3 и vlan4, поскольку предполагается в случае отсутствия связи между «маршрутизаторами» по внутреннему каналу, пытаться связаться по vlan-ам, настроенным на внешних интерфейсах;
  5. Возможно, в далеком светлом будущем переписать весь скрипт на имеющем больше возможностей языке. В данный момент есть желание выжать всё, что возможно из bash.


Вопросы


Разумеется, при написании, а в особенности после, возникло множество вопросов. Самый главный из них такой:
Имеются следующие переменные:


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 и многих других сайтов. К сожалению, многие ссылки со временем были утеряны, поэтому, если вы найдете здесь фрагменты откуда-либо, то я с удовольствием добавлю на них ссылки. Отдельная благодарность Алексею Ересько и Валерию Друба за консультации и помощь в решении сложностей в процессе подготовки и написания скрипта, а также Олегу Матусевичу за помощь в подготовке статьи.

З.Ы. При использовании материалов данной статьи обязательно указывать ссылку на источник и автора.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 29

    +2
    Честно говоря, какая-то фигня у вас написана в разделе «способы решения задачи». Причем тут L3 коммутаторы? Почему NAT отдельным пунктом, когда это по факту дополнение к любому из других вариантов?

    Фактически, способов фейловера три (возможно их комбинировать):
    1) BGP с full view. Провайдер прислал маршрут? Идем. Отозвал? Не идем.
    2) Разного рода костыли, определяющие доступность расположенных по ту сторону канала ресурсов. Соответственно, единственное сложное в этом случае — определить критерии, по которым канал считается живым/мертвым.
    3) Непосредственный анализ транзитного трафика на предмет того, что с ним происходит.

    Третий вариант — экзотика, и уж точно отсутствует на L3 коммутаторах, которые, вопреки вашим словам, не гибкие, а наоборот — тугие и весьма безмозглые, и катастрофически отстают по возможностям от других решений. Первые два неплохи, особенно вместе.

    А еще прокси-сервер, внезапно, может стоять inline, ничего прописывать нигде не придется. А если off-path — есть много способов автоматически скормить конечным системам его контакты.
    Сервер, в свою очередь, анализирует полученные данные и определяет, какой канал и на каком «маршрутизаторе» на текущий момент приоритетен. Именно для этого в статье рассмотрены настройки DHCP сервера, т.к. для изменения шлюза будут меняться настройки dhcpd.

    Я правильно понял, что вы средствами DHCP будете переключать клиентов между серверами? Если так, то поздравляю — это занимает почетное первое место в моем списке наиболее омерзительных архитектурных решений.

    И почему на схеме изображена жирная единая точка отказа — L2 свитч?
      0
      Причем тут L3 коммутаторы?


      Стояла задача — максимально охватить круг возможных решений. Данный вариант было сложно обойти стороной.

      Почему NAT отдельным пунктом, когда это по факту дополнение к любому из других вариантов?


      Имеется ввиду именно балансировка правилами NAT.

      Я правильно понял, что вы средствами DHCP будете переключать клиентов между серверами?


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

      Именно из-за весьма смутного представления, каким образом реализовывать задумки далее и стоит ли вообще(т.к. возможно это пустая трата времени и изобретение давно придуманного и прекрасно работающего велосипеда) и была написана статья.

      И почему на схеме изображена жирная единая точка отказа — L2 свитч?


      Да, я прекрасно понимаю, что с точки зрения надежности и стабильности логичнее было бы использовать к примеру 2 неуправляемых коммутатора или те же 2 L2 коммутатора. Но на схеме изображена реально работающая система и она пока что выглядит именно так.
        +1
        Данный вариант было сложно обойти стороной.

        А чем он отличается от остальных помимо аппаратной реализации data plane (вместо программной) и следующих из этого ограничений?

        Есть и аппаратные маршрутизаторы. Некоторые из которых способны смотреть в data plane и на основании анализа сиквенсов TCP за пару секунд фиксировать потери пакетов или пропадание связи (без всяких отдельных пингалок) и предпринимать соответствующие меры. Но такой функционал, конечно, и в софтовых решениях имеется. L3 свитчи о таком и не мечтают.
        Имеется ввиду именно балансировка правилами NAT.

        Все платформы разные, всякое бывает, но обычно при отсутствии записи в state table (т.е. новое соединение) первым отрабатывает роутинг, и только потом — NAT. Обычно. Есть исключения, но принципиально от них ничего не меняется, суть та же, просто правила NAT начинают притворяться таблицей маршрутизации.
        Какие ваши предложения?

        В данной топологии напрашивается VRRP разумеется. Это обеспечивает резервирование первого хопа и переключение на резерв за доли секунды прозрачно для клиента (если правильно настроить и если сбой будет тривиальным). Первые хопы будут active/standby со стороны LAN и active/active в сторону LAN. Дальнейшее распределение трафика от LAN по WAN линкам можно организовать на самих роутерах, т.е. перекидывать трафик между собой по мере надобности (например, ради балансировки или при фейловере), благо LAN емкость будет существенно больше WAN емкости. Есть варианты, чтобы трафик от клиента до первого хопа тоже распределялся между узлами, но это по многим причинам плохая идея.
        т.к. возможно это пустая трата времени и изобретение давно придуманного и прекрасно работающего велосипеда

        Ну в принципе да. Простейшая топология, best practices имеются для любых вендоров/решений.
          0
          А чем он отличается от остальных помимо аппаратной реализации data plane (вместо программной) и следующих из этого ограничений?

          Ну в основном, конечно, ценой…
          В данной топологии напрашивается VRRP разумеется.

          Спасибо. Изучу.
            0
            Ну в основном, конечно, ценой…

            Тогда вы выбрали неправильный пример. «Аппаратные маршрутизаторы» в целом стоят дороже L3 свитчей (а если смотреть на стоимость порта, то это как авианосец по сравнению с надувной лодкой). Архитектура похожая, но в чипах реализован более серьезный функционал.

            Ну и «Обычно цены на подобные устройства лежат за пределами 50 т. р.» вызывает улыбку :) Чтобы получить стоимость серьезных железок, вдобавок операторского класса (со всеми лицензиями и модулями на вкус), надо «р.» заменить на "$", иногда к получившемуся прибавить еще нолик. То, что можно купить за полтора килобакса из аппаратного, можно будет использовать именно в режиме умеющего маршрутизировать свитча — под шлюз в интернет оно не годится вообще хотя бы по причине отсутствия NAT, возможности работать с потоками, ограниченности размеров FIB, ограниченности программного функционала (экономят) и так далее.
              0
              Чтобы получить стоимость серьезных железок, вдобавок операторского класса (со всеми лицензиями и модулями на вкус), надо «р.» заменить на "$", иногда к получившемуся прибавить еще нолик.

              К сожалению, не большой опыт работы с данными железками и, судя по указанной вами цене, еще не скоро представится шанс. Цену указывал для контраста, старался не взять слишком высоко. В целом — принял к сведению, в будущем учту.
      0
      Почему прокси требует дополнительной настройки на стороне клиентов?
      Разве прозрачный прокси не убирает данный минус?
        0
        Разве прозрачный прокси не убирает данный минус?

        Да, но шифрованный трафик через нее не пропустишь.
          0
          Вообще никакой разницы нет.
            0
            Вообще никакой разницы нет.

            Относительно конкретно данного вопроса — у меня лично опыта использования подобных систем практически нет. Поэтому ориентируюсь исключительно на мнение более опытных знакомых или общественности. Итог по данному вопросу пока что такой: разница между шифрованным и не шифрованным трафиком с точки зрения использования прокси всё же есть. Вот два варианта:
            1
            2
            В случае первого — настройка на клиентах требуется, второго — требуется приобретение сертификата, а это деньги и время (зато не требуется настройка у клиентов). Это как раз те самые дополнительные сложности, которые я и имел ввиду, хотя не исключаю того варианты, что они несколько преувеличены.
              0
              Немного не так.

              Есть две совершенно независимые задачи:
              1) Доставить трафик до прокси.
              2) На проксе что-то вытворять с трафиком. Например — разобрать SSL.

              Первое может решаться установкой прокси в inline (тогда сетевое оборудование будет гнать трафик наружу/внутрь через прокси, полностью прозрачно для клиента) или off-path (тогда клиент будет инкапсулировать HTTP и адресовать трафик непосредственно прокси).

              В обоих случаях как минимум на винде никаких дополнительных настроек не требуется. Можно подсунуть WPAD по DHCP или DNS, там будет описано к чему идти через прокси и какой адрес у той прокси. По умолчанию у винды стоит галка «автоматически определять настройки прокси», это оно и есть. Никакой ручной работы.

              Вторая задача — классический MITM. Прокси на лету генерирует сертификат для домена, к которому обратился клиент. Если ее CA в доверенных у клиента, то клиент не заругается. Если не в доверенных — заругается. О покупке сертификата речь в любом случае не идет, никто не даст вам подписанный доверенным всеми ОС и браузерами CA сертификат на не принадлежащий вам домен.

              Но повторюсь, обе задачи совершенно независимые, решение одной не влияет на другую. Разница только в «будет ли клиент инкапсулировать внешний трафик и слать его напрямую проксе?».
        0
        вочдог раз в час запускается?
          0
          вочдог раз в час запускается?

          Watchdog запускается раз в час в любом варианте, чтобы проверить, что Daemon работает и, возможно, запустить его. Также Watchdog может быть вызван другими модулями в случае неожиданных значений переменных.

          Частота запуска из cron не должна превышать:
          $TESTERMAXDELAY + $JUDGEMAXDELAY + $LOGGERMAXDELAY + 30

          В указанном в статье примере конфигурационного файла частота должна быть не меньше 2 минут, но на мой взгляд — запуска 1 раз в час вполне достаточно.
            0
            я бы максимально часто запускал вочдог во избежании продолжительной неработоспособности демона
              0
              я бы максимально часто запускал вочдог во избежании продолжительной неработоспособности демона

              Не уверен, что это оправдано, хотя с другой стороны, конечно, и не повредит. Но за прошедшие шесть месяцев модуль Daemon ни разу самопроизвольно не выключался, так что он сделан, скорее, для подстраховки, нежели, как жизненная необходимость. Конечно же, всегда остается вероятность того, что мне несказанно везло весь вышеописанный промежуток времени, но в это слабо верится.
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            0. баш во фре не родной.

            Для меня более привычный. Возможно, если будет решено оставить данную, не побоюсь этого слова, программу именно на скриптовом языке, — рассмотрю варианты реализации на sh или другом родном для FreeBSD языке.

            2. rc.d — у вас от десктопа, что за треш!?

            Местами осталось от старой настройки. По поводу языка с вами соглашусь. Относительно мышки — при работе непосредственно на сервере иногда помогает. Остальное еще раз изучу, возможно, действительно лишнее.

            3. Для мелкой конторы без своих зон вместо бинд лучше unbound, а вместо dhcpd — dnsmasq (с отключённым днс и тфтп).

            Возможно, но уже изучен bind и isc-dhcpd и, честно говоря, их работа вполне устраивает и изучать/настраивать что-то другое не очень хочется. К тому же имеется вероятность, что своя зона все же появится рано или поздно.

            можно тестить все имеющиеся подключения одновременно.

            Они итак тестируются одновременно. как раз при помощи setfib. Однако, нюансы использования именно SO_SETFIB из вашего замечания изучу.
            • НЛО прилетело и опубликовало эту надпись здесь
            +1
            собственно, Дмитрий уже сказал про самый приемлемый вариант (на этом оборудовании) — стандартный VRRP.
            Странно, что в списке решений автор перепрыгнул от SOHO-маршрутизаторов сразу к операторским крммутатором, обойдя своим вниманием огромный сегмент маршрутизаторов уровня предприятий. Их у каждого серьезного производителя не по одному в линейке предстаалено, и с поставленой задачей, подозреваю, справятся они не хуже скриптов на фряхе
              0
              И, в случае, если мы заранее установили a=1, данное выражение будет означать 123, а если a=2, то 321
              Вам нужен eval. Примеров достаточно в /etc/rc.d.
                0
                Вам нужен eval. Примеров достаточно в /etc/rc.d.

                Спасибо огромное. Похоже, что это именно то, что нужно!
                +3
                Типовой костыль «дла балансировки интерента», сделано для офиса (Pentium 3, 512 ОП), офисным админом.
                О BGP речи не идёт, 2 провайдера 2 IP, 2 провода — с точки зрения автора это настолько очевидно, что даже не заслуживает упоминания.
                Скрипту несколько лет, он был переписан и ни слова про VRRP, а лучше CARP если уж FreeBSD.
                Вобще, ИМХО, скрипт на бюджетном сервере, нормальное решение для офиса. Но, понимаете, так принято, что каждый пишет свой костыль скрипт, а потом ссыт кипятком бегает как угорелый и всем его показывает. Предлагая своё готовое решение вы лишаете других администраторов такой радости.
                А если кто-то решит использовать ваш, то почему он должен предпочесть его любому другому из пункта 3. Сами же туда попадаете, сделайте хоть какое-то сравнение/обзор, а то «проще написать свою версию, нежели разбираться в чужой». Так и в вашей никто разбираться не будет…
                  0
                  Типовой костыль «дла балансировки интерента», сделано для офиса (Pentium 3, 512 ОП), офисным админом.

                  Так точно. Только я и публикую по этой самой причине — сделать из «типового костыля» что-то приемлемое.

                  О BGP речи не идёт, 2 провайдера 2 IP, 2 провода — с точки зрения автора это настолько очевидно, что даже не заслуживает упоминания.

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

                  каждый пишет свой костыль скрипт, а потом ссыт кипятком бегает как угорелый и всем его показывает

                  Хочу заметить, что я его показываю не от большой гордости, а ровно с двумя целями: 1. исправление проблем/улучшение алгоритма, 2. помощь другим «офисным админам» с решением подобной задачи.

                  А если кто-то решит использовать ваш, то почему он должен предпочесть его любому другому из пункта 3. Сами же туда попадаете, сделайте хоть какое-то сравнение/обзор, а то «проще написать свою версию, нежели разбираться в чужой». Так и в вашей никто разбираться не будет…

                  Здравая мысль, товарисч! К сожалению, мне она сразу не пришла в голову, посему получилось то, что получилось. Ну относительно плюсов как минимум 1 очевидный назвать могу с ходу — модульность. Если не нравится, к примеру, система тестирования соединения — переписываешь модуль Tester и получаешь то, что лично тебе нужно и т.д., при этом всё остальное будет взаимодействовать с новым модулем также, как и раньше, не обращая внимание, что изменилась внутренняя логика. Разумеется, это работает, если в новом модуле не нарушен ввод-вывод, относительно старого. По поводу сравнения — какое ваше мнение, писать новую статью(на мой взгляд нет причин для отдельной публикации) или изложить это самое сравнение в комментариях?
                    0
                    Не, серьёзно, сначала разберитесь с CARP, а потом пишите сравнение с ним в том числе.
                    после этого даже желание писать может пропасть.

                    балансировка и отказоустойчивость за время порядка секунды,
                    при этом без скриптов, а в конфигах.
                    CARP — очень крут для таких целей.
                  0
                  Попробуйте PfSense — очень милый дистрибутивчик FreeBSD, ставится за две минуты, есть нескучный WEB-интерфейс с дашбоардами (можно начальству графики показывать), вагон дополнительных пакетов, есть CARP (это когда один сервер падает, а второй это видит и его LAN-адрес забирает). А вообще, очень интересна тема балансировки двух каналов (так, чтобы оба одновременно работали и не были завязаны на статические маршруты), когда у вас один сервер, два WAN и один LAN.
                    0
                    Попробуйте PfSense — очень милый дистрибутивчик FreeBSD, ставится за две минуты, есть нескучный WEB-интерфейс с дашбоардами (можно начальству графики показывать),

                    Слышал, смотрел картинки. В вашем варианте один сервер подменяет другой, при этом нет гарантии, что второй правильно распознает падение первого. Мне по этой причине кажется более помехоустойчивым кажется вариант, когда сервера работают параллельно и делят нагрузку + подключены к единой системе мониторинга и подсчета трафика. Я для этих целей использую Zabbix и NetAMS.

                    есть CARP

                    Данную функцию я еще детально не изучил, но с большой вероятностью буду использовать в своей системе в ближайшем будущем.

                    А вообще, очень интересна тема балансировки двух каналов (так, чтобы оба одновременно работали и не были завязаны на статические маршруты), когда у вас один сервер, два WAN и один LAN.

                    Данная тематика вскользь упомянута в статье. Лично мне известны 2 варианта балансировки — это посредством NAT(во всяком случае при использовании «ядерного» NAT и скорее всего здесь навязывается ipfw) и с помощью proxy сервера, в частности, squid. Статей по данному вопросу хватает. Разве что, я не вдавался в детали относительно статических маршрутов…
                      0
                      Данная тематика вскользь упомянута в статье. Лично мне известны 2 варианта балансировки — это посредством NAT(во всяком случае при использовании «ядерного» NAT и скорее всего здесь навязывается ipfw) и с помощью proxy сервера, в частности, squid. Статей по данному вопросу хватает. Разве что, я не вдавался в детали относительно статических маршрутов…


                      Я, честно говоря, с ipfw практически не сталкивался, а в iptables это делается довольно изящно, через маркировку соединений (не знаю, есть ли аналог в ipfw) и создание правил маршрутизации (по принципу «если есть метка — отправляем сюда»). Хорошие люди, оказывается, написали статью: http://habrahabr.ru/post/173713/.Комментарии там тоже весьма полезны. Правда, в статье описывается случай, когда вам нужно балансировать внешний трафик внутрь сети, но ничего не мешает развернуть этот принцип «наоборот».
                      • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    Чего не сделают эти русские, чтобы не использовать BGP.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое