Автоматизация работы со статическими маршрутами на сети FreeBSD-серверов

С чего всё началось


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

Реализация


Исходим из того, что у нас есть некоторое количество серверов под управлением FreeBSD, и при добавлении нового маршрута (например, на одном из этих серверов была затерминирована новая клиентская сеть) он должен быть прописан на всех этих серверах.
Для начала создадим текстовый файл, каждая строчка которого являет собой адрес одного сервера, и для каждого из этих серверов настроим авторизацию по ключу (как это сделать, описано, например, здесь).
Пример файла-списка серверов:
192.168.0.1
192.168.1.1
mainserver.yourdomain.ru


Дальнейшую работу будет делать связка из двух shell-скриптов:
1) checkroute.sh [-c] destination gateway, где
-c — опция check-only (только проверить соответствие, не внося изменений),
destination — IP адрес или сеть назначения,
gateway — шлюз для адреса назначения.
Этот скрипт проверяет валидность введённых параметров скрипта и, подключаясь по очереди к каждому из серверов из списка по ssh, копирует на него второй скрипт и запускает его.
2) checker.sh destination gateway ifcheck
В свою очередь, проверяет текущую таблицу маршрутизации и конфигурационные файлы, докладывает о результатах проверки и, если первый скрипт был запущен без опции -c, приводит соответствующие записи в соответствие с парой destination/gateway.

checkroute.sh

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

ifcheck="no"
while getopts "c" optname
	do case "$optname" in
		"c") ifcheck="check" ;;
		*)   echo "Usage: $0 [OPTIONS] DESTINATION GATEWAY
\t-c\tcheck route, don't correct it
"
			exit
			;;
		esac
	done
shift $(($OPTIND -1 ))

Следующим шагом будет проверка на валидность параметров DESTINATION и GATEWAY, переданных скрипту. Для наших целей необходимо, чтобы в качестве DESTINATION могли выступать IP адрес или сеть, а в качестве GATEWAY — только IP адрес.

validIPregexp="(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
checkip () {
	CHECK=$(echo $1 | grep -E "^$validIPregexp$2")
	if [ ! "$?" -eq 0 ]
		then
		echo "Incorrect $3: $1, please try again. "
		exit
		fi
}
checkip $1 "(/([0-2]?[0-9]|3[0-2]))?$" "destination IP address or network"
checkip $2 "$" "gateway"

Далее построчно читаем файл $serverlist и для каждого сервера из списка инициализируем подключение по ssh, копируем и запускаем скрипт checker.sh. В переменной $dir хранится путь до директории, в которой лежат файлы $serverlist и checker.sh

serverlist='servers.lst'
dir='/usr/local/etc/your_dir'
cat "${dir}/$serverlist" | while read server
	do
	echo "$server: "
	cat "${dir}/$checker.sh | ssh -q $server "rm -f /var/tmp/checker.sh ; cat - >> /var/tmp/checker.sh ; sh /var/tmp/checker.sh $1 $2 $ifcheck "
	done

checker.sh

Теперь рассмотрим, что же делает скрипт checker.sh, будучи запущенным на одном из наших серверов.
Функция, применяющая изменения, если скрипт script.sh был запущен без опции -c

ifcheck=$3
commit () {
	if [ $ifcheck != "check" ]
		then
		eval "$1" 2> /dev/null
		if [ ! $? -eq 0 ]
			then
			echo "Couldn't process the following command:
$1"
			else echo "$2"
			fi
		else echo "To correct it run this script without \"-c\" option or process the following command manually:
$1
"
		fi
}


Разбираем текущую таблицу маршрутизации (вывод netstat -rnW) и результат записываем в $CurrentGW
Возможные варианты:
1) Адрес назначения затерминирован на локальном интерфейсе ($CurrentGW=«LOCAL»)
2) Адрес назначения затерминирован где-то ещё и присутствует в таблице маршрутизации, тогда $CurrentGW будет присвоен текущий шлюз для этого адреса
3) Адрес назначения затерминирован где-то ещё и отсутствует в таблице маршрутизации. В таком случае, $CurrentGW останется пустой.

CurrentGW=`netstat -rnW | awk -v gw="$2" -v src="$1" '{ if ( $1 ~ src ) { if ( $2 ~ /'$validIPregexp'/ ) { if ( $2 !~ gw ) { print $2 } else { print "OK" } } else { print "LOCAL" } } }'`
if [ -z $CurrentGW ]
	then
	echo "Route for $1 does't exist."
	commit "sudo route add $1 $2" "Adding route."
	else
	case $CurrentGW in
	OK )  echo "Current route to $1 is $2 : OK" ;;
	LOCAL ) echo "It's on local interface" ;;
	* ) echo "Route for $1 exists but destination is wrong: $CurrentGW instead of $2 " ; commit "sudo route change $1 $2" "Changing it." ;;
	esac
	fi

Далее разбираем конфигурационный файл, полный путь к которому лежит в переменной $filename. Для этого выбираем из него все строки, описывающие добавление маршрутов, во временный файл и проверяем существование записи для адреса назначения. Проводится аналогично разбору вывода netstat -rnW выше.

filename='/usr/local/etc/rc.d/rc.script.for.adding.routes.sh'
cat "$filename" | grep -v '^#' | sed '/^$/d' | sed 's/[ \t]+/ /g' | grep -E 'route.+add' > /var/tmp/routes_temp.lst
FileGW=`cat /var/tmp/routes_temp.lst | grep $1 | awk -v gw="$2" '{  if ( $NF ~ gw ) { print "YES" } else { print $NF } }'`
if [ $CurrentGW = "LOCAL" ]
	then if [ ! -z $FileGW ]
		then
		echo "It's on local interface, but route for $1 was found in $filename. "
		commit "sudo sed -Ei '' '\:route[^\n]+add[^\n]+$1:' $filename" "Deleting it from there."
		fi
	else
	if [ -z $FileGW ]
		then
		echo "Checked $filename: no match. "
		t1=`tail -1 /var/tmp/routes_temp.lst | awk '{ print $(NF-1) }'`
		t2=`tail -1 /var/tmp/routes_temp.lst | awk '{ print $NF }'`
		IFS=''
		regex="\\:route[^\\n]+add[^\\n]+$t1[ \\t]+$t2:a\\
		/sbin/route -nq add $1 $2

		"
		commit "sudo sed -Ei '' '$regex2' $filename" "Adding route to the end of file"
		else
		if [ $FileGW != "YES" ]
			then
			echo "Record for route to $1 found, but gateway is wrong: $FileGW. "
			regex="s|$1[ \t]+$FileGW|$1 $2|g"
			commit "sudo sed -Ei '' '$regex2' $filename" "Changing gateway to $2"
			else echo "Correct route was found in $filename: OK"
			fi
		fi
fi

Хочу прояснить пару моментов из последнего куска кода. Переопределение системной переменной IFS понадобилоть для того, чтобы создать многострочную переменную $regex, что, в свою очередь, продиктовано синтаксисом sed. Такой способ добавления строки в файл был выбран потому, что в $filename маршруты могут добавляться внутри одной из функций, как это происходит в моём случае. Если же для вас это не критично, вы можете обойтись конструкцией вида

commit "sudo echo '/sbin/route -nq add $1 $2' >> $filename"

Заключение


Описанные выше скрипты можно адаптировать и под другие *nix системы, вся разница — в ключах sed (для Debian, например, -ri вместо -Ei "") и синтаксисе команды route (см. man route). Функционал скриптов, как было уже сказано выше, легко расширяется по аналогии.
В скриптах довольно много проверок («защита от дурака»), однако ответственность за правильность пары destination/gateway ложится на администратора, запускающего скрипт. Будьте бдительны!
Полные тексты обоих скриптов прилагаются: checkroute.sh, checker.sh
Поделиться публикацией

Похожие публикации

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

    0
    А еще можно сделать гит рерозиторий, и править в проекте файл с данными на одной машине, а остальные допустим по крону раз в минуту делают git pull и всё. Имхо, менее геморойно.
      0
      Да, вариант вполне жизнеспособен, однако я работал в условиях, когда серверную часть желательно было оставить как есть (кое-кто придерживается старого правила «работает — не трогай»). Поэтому я и выбрал вариант, в котором инициализация работы происходит со сторонней машины.
      Если изменять серверную часть, то, наверное, лучше было бы сразу посмотреть в сторону динамической маршрутизации (тот же bird).
      +9
      Зачем же так сложно, неудобно и некрасиво? Все прогрессивное человечество вот уже 30 лет пользуется протоколами динамической маршрутизации.
        0
        Присоединяюсь к вопросу. На юниксах есть пакет zebra, который позволяет настроить и RIP и OSPF. Раз настроили и забыли.
          0
          Иногда динамика невозможна. Скажем, пакет Quagga не умеет работать с «виртуальным» ip-адресом carp-интерфейсов, упорно подсовывая в анонсах «реальный» ip-адрес, что во многих случаях неудобно. Авторы Quagga в рассылке заявили, что и не будут это исправлять, типа это фича.

          В остальном соглашусь. И скрипты что-то очень уж… гм… монстроидальны. Центральный репозиторий svn/git был бы куда изящней.
            0
            Я вообще не фанат маршрутизации на Linux, все-таки, роутинг при помощи CPU — это уже не модно.

            Для малого офиса, конечно, сойдет, пока поток через роутер не превышает 2-3 Мб/с, а количество сетей — 3-4 штук. Но дальше уже надо бы на специализированное оборудование переходить, желательно с Cisco Express Forwarding, чтобы маршрутизацией занимались исключительно ASIC, а процессор освободить для управления этой и другими функциями.
              0
              Гм, вы видимо в провайдерских сетях не работали ;) Маршрутизация на CPU это очень даже модно. Гигабит, причём с NAT'ом, маршрутизировать на серверах и под Linux — милое дело, стоит на порядок дешевле цисковских (и не только) NAT-железок. А уж чистый роутинг на этих скоростях вытянет любой современный офисный тазик с нормальной сетевушкой.
                0
                Работал :-) Я, кагбэ, исключительно с провайдерами и работаю. Правда, не нашими. К примеру, сейчас работаю с Sprint, одним из 12 Tier 1 операторов.

                Чистый роутинг на 1 Гб/с между 10 разными сетями — не вытянет, готов поспорить :-) То есть, итоговая скорость не будет 1 Гб/с из-за задержек на обработку пакетов. А уж цепочка таких роутеров сделает задержку совсем неприличной.
                  0
                  Ну давайте поспорим, не вопрос :) С многоядерными процессорами и интеловскими картами с поддержкой нескольких очередей MSI-X влёгкую.
                    0
                    :-)

                    Это бессмысленный спор и бессмысленный разговор, с учетом того, что я не могу показать Вам живую работу этого всего :-)

                    Поставьте эксперимет, если Вам интересно: подключите каскадом несколько компов на Linux с настроенным роутингом и рядом — несколько роутеров с поддержкой Cisco Express Forwarding, и просто попингуйте сеть на одном конце цепочки с другого ее конца. И посмотрите на delay.

                    Для того, чтобы ускорить маршрутизацию пакетов при помощи центрального процессора, когда-то придумали MPLS. Cisco Express Forwarding позволяет маршрутизировать обычные пакеты с той же скоростью, с которой это делают P роутеры в MPLS сети по меткам. Никогда процессором вы не добьетесь этой скорости.
                      0
                      Ну да, бессмысленный спор за отсутствием реального приложения. Но говорить, что нужна циска при потоках, превышающих 2-3 Мбайт/сек, является уж явным и абсолютным преувеличением.
          0
          Добро пожаловать на Хабр.
            0
            Спасибо!
            +8
            Обалдеть.

            Я конечно что-то не понимаю наверное, но это очень кривые костыли, которые просто из неправильной архитектуры сети вытекают.

            Поднимите или демоны динамической маршрутизации (типа quagga или zebra) на машинках или dhcp rfc смотрите:

            tools.ietf.org/html/rfc2132

            5.8. Static Route Option

            This option specifies a list of static routes that the client should
            install in its routing cache. If multiple routes to the same
            destination are specified, they are listed in descending order of
            priority.
              +3
              Вариант с DHCP может не подойти, ибо изменения очень инертны получаются. а вот включить тупой RIP
              командной router_enable="YES", действительно не сложно
                +2
                Безусловно, динамика лучше.

                Но если уж гоняют скрипты — то попросить dhcpcd проапдейтить руты (одна команда вместо тонны ненужных скриптов — дефакто нужно дать только SIGHUP сигнал демону) — все равно сильно лучше и проще предложенного решения.
                +2
                Согласен, что «неправильная» (устаревшая) архитектура сети — ключ ко всему. Я работал с тем, что есть, о чём писал в комментарии выше. Топик — это описание реализации конкретного решения конкретной задачи, плюс небольшое описание некоторых особенностей shell-скриптов (работа с опциями скрипта, передача команды в качестве параметра функции), кому-то может пригодиться. И я не уверен, что запуск dhclient'a на серверах будет одобрен свыше. Но за наводку спасибо, об этом я, признаться, не подумал.
                +8
                ребята, давайте я вам конфиги для OSPF напишу, а? Забесплатно :)
                  0
                  Спасибо, особенно за бескорыстие — это в наше время довольно редкое явление =) Но, если дадут добро на OSPF, мы и сами как-нибудь справимся =)
                  +5
                  Извините, а зачем написана эта статья? Я восхищаюсь Вашим трудолюбием и понимаю, что в Вашем случае внедрение OSPF может быть и невозможно («я работал в условиях, когда серверную часть желательно было оставить как есть»), но зачем описывать это здесь? Вряд ли кто-нибудь пойдёт удалять свою кваггу или bird и адаптировать под себя выложенные скрипты.
                    +1
                    Прошу прощения, что повторяюсь в комментариях, но отвечу и здесь. Топик — это описание реализации конкретного решения конкретной задачи, плюс небольшое описание некоторых особенностей shell-скриптов (работа с опциями скрипта, передача команды в качестве параметра функции), я посчитал, что кому-то может пригодиться (раз схема со статическими маршрутами работает у нас, то она вполне может встретиться и где-то ещё). Я ни в коем случае не призываю отказываться от динамической маршрутизации или от своих наработанных схем. =)
                    +7
                    image
                      +2
                      что люди только не делают чтобы не использовать какой-либо igp, хотя бы тот же rip и коробочного routed O_o
                        +1
                        А я всегда настраивал рип или оспф, думаю тут это тоже уместно.
                          +2
                          Free-BSDшники шальные люди.

                          Титанический труд автора безусловно заслуживает уважение за исключением одной мелочи — постановка такой задачи полный идиотизм! Несколько десятков лет огромное количество людей разрабатывают протоколы маршртизации для того чтобы вот ЭТО НЕ ДЕЛАТЬ вручную.
                            +2
                            «я не переписываю телнетд! мне лень его исходники качать!!» (c баша, не смог удержаться =) )
                              0
                              Мы не шальные, это автор… гм… тот ещё выдумщик. :)
                              0
                              автор зверь, перелопатил на С свою статическую маршрутизацию сети. есть еще умельцы!
                              долой динамические протоколы циски и прочую ересь! даешь С! шучу )

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

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